diff --git a/fern/pages/changelogs/php-sdk/2024-12-12.mdx b/fern/pages/changelogs/php-sdk/2024-12-12.mdx new file mode 100644 index 00000000000..c131f3473d3 --- /dev/null +++ b/fern/pages/changelogs/php-sdk/2024-12-12.mdx @@ -0,0 +1,3 @@ +## 0.2.2 +**`(fix):`** Handle cross package type name deconfliction + diff --git a/generators/php/codegen/src/ast/Class.ts b/generators/php/codegen/src/ast/Class.ts index 7b26e5a8813..0daf64b3cba 100644 --- a/generators/php/codegen/src/ast/Class.ts +++ b/generators/php/codegen/src/ast/Class.ts @@ -75,6 +75,8 @@ export class Class extends AstNode { } public write(writer: Writer): void { + // required to fully de-conflict imports + writer.addReference(new ClassReference({ name: this.name, namespace: this.namespace })); if (this.abstract) { writer.write("abstract "); } diff --git a/generators/php/codegen/src/ast/ClassReference.ts b/generators/php/codegen/src/ast/ClassReference.ts index 2fc6f37f929..74a24707f62 100644 --- a/generators/php/codegen/src/ast/ClassReference.ts +++ b/generators/php/codegen/src/ast/ClassReference.ts @@ -13,15 +13,22 @@ export declare namespace ClassReference { export class ClassReference extends AstNode { public readonly name: string; public readonly namespace: string; + private fullyQualified: boolean; constructor({ name, namespace }: ClassReference.Args) { super(); this.name = name; this.namespace = namespace; + this.fullyQualified = false; + } + + public requireFullyQualified(): void { + this.fullyQualified = true; } public write(writer: Writer): void { writer.addReference(this); - writer.write(`${this.name}`); + const refString = this.fullyQualified ? `\\${this.namespace}\\${this.name}` : this.name; + writer.write(`${refString}`); } } diff --git a/generators/php/codegen/src/ast/core/Writer.ts b/generators/php/codegen/src/ast/core/Writer.ts index d81c548bc94..9b2adfe4d8e 100644 --- a/generators/php/codegen/src/ast/core/Writer.ts +++ b/generators/php/codegen/src/ast/core/Writer.ts @@ -3,7 +3,20 @@ import { BasePhpCustomConfigSchema } from "../../custom-config/BasePhpCustomConf import { ClassReference } from "../ClassReference"; import { GLOBAL_NAMESPACE } from "./Constant"; -type Namespace = string; +/* A fully qualified type name _without_ the initial backslash */ +type FullyQualifiedName = string; + +interface ParsedFullyQualifiedName { + namespace: string; + name: string; +} + +function parseFullyQualifiedName(rawFullyQualifiedName: string): ParsedFullyQualifiedName { + return { + namespace: rawFullyQualifiedName.substring(0, rawFullyQualifiedName.lastIndexOf("\\")), + name: rawFullyQualifiedName.substring(rawFullyQualifiedName.lastIndexOf("\\") + 1) + }; +} export declare namespace Writer { interface Args { @@ -25,7 +38,7 @@ export class Writer extends AbstractWriter { public customConfig: BasePhpCustomConfigSchema; /* Import statements */ - private references: Record = {}; + private references: Record = {}; constructor({ namespace, rootNamespace, customConfig }: Writer.Args) { super(); @@ -38,9 +51,24 @@ export class Writer extends AbstractWriter { if (reference.namespace == null) { return; } - const namespace = + + // If there's a naming conflict, tell the reference to use its qualified name + const conflictingReferences = Object.keys(this.references) + // Filter out the current namespace. + .filter((seenRef) => { + const parsed = parseFullyQualifiedName(seenRef); + return parsed.namespace !== reference.namespace && parsed.name === reference.name; + }); + + if (conflictingReferences.length > 0) { + reference.requireFullyQualified(); + return; + } + + const fullyQualifiedName = reference.namespace === GLOBAL_NAMESPACE ? reference.name : `${reference.namespace}\\${reference.name}`; - const references = (this.references[namespace] ??= []); + const references = (this.references[fullyQualifiedName] ??= []); + references.push(reference); } @@ -64,11 +92,7 @@ ${this.buffer}`; } let result = referenceKeys // Filter out the current namespace. - .filter((reference) => { - // Remove the type name to get just the namespace - const referenceNamespace = reference.substring(0, reference.lastIndexOf("\\")); - return referenceNamespace !== this.namespace; - }) + .filter((reference) => parseFullyQualifiedName(reference).namespace !== this.namespace) .map((ref) => `use ${ref};`) .join("\n"); diff --git a/generators/php/sdk/versions.yml b/generators/php/sdk/versions.yml index 053fc5e1c3e..6c87ba227ea 100644 --- a/generators/php/sdk/versions.yml +++ b/generators/php/sdk/versions.yml @@ -1,3 +1,9 @@ +- version: 0.2.2 + changelogEntry: + - type: fix + summary: >- + Handle cross package type name deconfliction + irVersion: 53 - version: 0.2.1 changelogEntry: - type: fix 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 c4ed45d1da8..55083f22862 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 @@ -3,20 +3,19 @@ namespace Seed\FolderB\Common\Types; use Seed\Core\Json\JsonSerializableType; -use Seed\FolderC\Common\Types\Foo; use Seed\Core\Json\JsonProperty; class Foo extends JsonSerializableType { /** - * @var ?Foo $foo + * @var ?\Seed\FolderC\Common\Types\Foo $foo */ #[JsonProperty('foo')] - public ?Foo $foo; + public ?\Seed\FolderC\Common\Types\Foo $foo; /** * @param array{ - * foo?: ?Foo, + * foo?: ?\Seed\FolderC\Common\Types\Foo, * } $values */ public function __construct( diff --git a/seed/php-sdk/seed.yml b/seed/php-sdk/seed.yml index 6d0d485133d..3716fba4f05 100644 --- a/seed/php-sdk/seed.yml +++ b/seed/php-sdk/seed.yml @@ -32,8 +32,6 @@ scripts: allowedFailures: # Basic auth is not supported yet. - basic-auth - # We aren't handling multiple types used in a class with the same name (from different packages). - - cross-package-type-names # Path parameter enums are not supported yet. - enum # Enums don't support the fromJson method yet.