@bazo/openapi-client-generator 3.0.13 → 3.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin.js CHANGED
@@ -18565,11 +18565,7 @@ function resolveServerUrl(url, variables) {
18565
18565
  });
18566
18566
  }
18567
18567
  function deriveServerName(server, index) {
18568
- const candidates = [
18569
- server["x-name"],
18570
- server.name,
18571
- server.description
18572
- ];
18568
+ const candidates = [server["x-name"], server.name, server.description];
18573
18569
  for (const candidate of candidates) {
18574
18570
  if (typeof candidate !== "string") {
18575
18571
  continue;
@@ -18629,7 +18625,7 @@ function applyStringConstraints(schema) {
18629
18625
  if (schema.pattern) {
18630
18626
  try {
18631
18627
  new RegExp(schema.pattern);
18632
- const escaped = schema.pattern.replace(/\\/g, "\\\\").replace(/\//g, "\\/");
18628
+ const escaped = schema.pattern.replace(/\//g, "\\/");
18633
18629
  parts.push(`.regex(/${escaped}/)`);
18634
18630
  } catch {
18635
18631
  }
@@ -18786,9 +18782,7 @@ function convertArray(schema, ctx) {
18786
18782
  // src/converter/composition.ts
18787
18783
  function convertAllOf(schema, ctx) {
18788
18784
  const schemas = schema.allOf;
18789
- const allObjects = schemas.every(
18790
- (s) => s.type === "object" || s.properties || s.refName && !s.allOf && !s.oneOf && !s.anyOf
18791
- );
18785
+ const allObjects = schemas.every((s) => s.type === "object" || s.properties || s.refName && !s.allOf && !s.oneOf && !s.anyOf);
18792
18786
  if (allObjects) {
18793
18787
  return mergeAllOfObjects(schemas, schema, ctx);
18794
18788
  }
@@ -18905,10 +18899,10 @@ function schemaToZod(schema, ctx) {
18905
18899
  if (schema.refName && ctx.namedSchemas.has(schema.refName)) {
18906
18900
  return schema.refName;
18907
18901
  }
18908
- if (schema.allOf) return convertAllOf(schema, ctx);
18909
- if (schema.oneOf) return convertOneOf(schema, ctx);
18910
- if (schema.anyOf) return convertAnyOf(schema, ctx);
18911
- if (schema.enum) return convertEnum(schema);
18902
+ if (schema.allOf) return maybeNullable(convertAllOf(schema, ctx), schema);
18903
+ if (schema.oneOf) return maybeNullable(convertOneOf(schema, ctx), schema);
18904
+ if (schema.anyOf) return maybeNullable(convertAnyOf(schema, ctx), schema);
18905
+ if (schema.enum) return maybeNullable(convertEnum(schema), schema);
18912
18906
  switch (normalizeType(schema)) {
18913
18907
  case "string":
18914
18908
  return convertString(schema);
@@ -19180,6 +19174,12 @@ var fullMode = {
19180
19174
  lines.push(` return (configure ? configure(http) : http) as Http;`);
19181
19175
  lines.push(`}`);
19182
19176
  lines.push("");
19177
+ lines.push(`export interface ${clientName} {`);
19178
+ for (const op of ops) {
19179
+ lines.push(` ${op.operationId}: ReturnType<typeof ${op.operationId}>;`);
19180
+ }
19181
+ lines.push(`}`);
19182
+ lines.push("");
19183
19183
  lines.push(`export class ${clientName} {`);
19184
19184
  if (defaultServer) {
19185
19185
  lines.push(` static readonly servers = ${clientName}Servers;`);
@@ -19190,10 +19190,6 @@ var fullMode = {
19190
19190
  lines.push(` private operations: Map<string, Function>;`);
19191
19191
  lines.push(` public http: Http;`);
19192
19192
  lines.push("");
19193
- for (const op of ops) {
19194
- lines.push(` declare ${op.operationId}: ReturnType<typeof ${op.operationId}>;`);
19195
- }
19196
- lines.push("");
19197
19193
  if (defaultServer) {
19198
19194
  lines.push(` constructor(baseUrl: string, queryClient: QueryClient, options?: RequestInit, onHttpConfigure?: (w: Http) => unknown);`);
19199
19195
  lines.push(` constructor(queryClient: QueryClient, options?: RequestInit, onHttpConfigure?: (w: Http) => unknown);`);
@@ -19384,10 +19380,7 @@ var playwrightMode = {
19384
19380
  name: "playwright",
19385
19381
  operationsInline: true,
19386
19382
  generateImports(_ctx) {
19387
- const lines = [
19388
- 'import { z } from "zod";',
19389
- 'import { type APIRequestContext, type APIResponse } from "@playwright/test";'
19390
- ];
19383
+ const lines = ['import { z } from "zod";', 'import { type APIRequestContext, type APIResponse } from "@playwright/test";'];
19391
19384
  return lines.join("\n");
19392
19385
  },
19393
19386
  generateHelperTypes(_ctx) {
@@ -20219,6 +20212,8 @@ function generateSwiftMutation(op, namedSchemas, schemaLookup) {
20219
20212
  // src/modes/swift/index.ts
20220
20213
  var swiftMode = {
20221
20214
  name: "swift",
20215
+ nativeTarget: true,
20216
+ fileExtension: ".swift",
20222
20217
  operationsInline: true,
20223
20218
  generateImports(_ctx) {
20224
20219
  return ["import Foundation", "#if canImport(FoundationNetworking)", "import FoundationNetworking", "#endif"].join("\n");
@@ -20397,11 +20392,15 @@ var swiftMode = {
20397
20392
  lines.push(` }`);
20398
20393
  lines.push("");
20399
20394
  lines.push(` private func documentedResponse<T: Sendable>(_ payload: T, from response: ApiResponse<Data>) -> APIError {`);
20400
- lines.push(` APIError.documentedResponse(statusCode: response.status, payload: payload, headers: response.headers, url: response.url)`);
20395
+ lines.push(
20396
+ ` APIError.documentedResponse(statusCode: response.status, payload: payload, headers: response.headers, url: response.url)`
20397
+ );
20401
20398
  lines.push(` }`);
20402
20399
  lines.push("");
20403
20400
  lines.push(` private func undocumentedResponse(from response: ApiResponse<Data>) -> APIError {`);
20404
- lines.push(` APIError.undocumentedResponse(statusCode: response.status, data: response.data, headers: response.headers, url: response.url)`);
20401
+ lines.push(
20402
+ ` APIError.undocumentedResponse(statusCode: response.status, data: response.data, headers: response.headers, url: response.url)`
20403
+ );
20405
20404
  lines.push(` }`);
20406
20405
  lines.push("");
20407
20406
  lines.push(` private func decodeJSON<T: Decodable>(_ type: T.Type, from response: ApiResponse<Data>) -> T? {`);
@@ -21024,6 +21023,8 @@ function generateKotlinMutation(op, namedSchemas, schemaLookup) {
21024
21023
  // src/modes/kotlin/index.ts
21025
21024
  var kotlinMode = {
21026
21025
  name: "kotlin",
21026
+ nativeTarget: true,
21027
+ fileExtension: ".kt",
21027
21028
  operationsInline: true,
21028
21029
  generateImports(_ctx) {
21029
21030
  const lines = [
@@ -21162,6 +21163,967 @@ function quoteKotlinString(value) {
21162
21163
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t")}"`;
21163
21164
  }
21164
21165
 
21166
+ // src/converter/schema-to-php.ts
21167
+ var PHP_RESERVED = /* @__PURE__ */ new Set([
21168
+ "abstract",
21169
+ "and",
21170
+ "array",
21171
+ "as",
21172
+ "bool",
21173
+ "break",
21174
+ "callable",
21175
+ "case",
21176
+ "catch",
21177
+ "class",
21178
+ "clone",
21179
+ "const",
21180
+ "continue",
21181
+ "declare",
21182
+ "default",
21183
+ "do",
21184
+ "echo",
21185
+ "else",
21186
+ "elseif",
21187
+ "empty",
21188
+ "enddeclare",
21189
+ "endfor",
21190
+ "endforeach",
21191
+ "endif",
21192
+ "endswitch",
21193
+ "endwhile",
21194
+ "enum",
21195
+ "eval",
21196
+ "exit",
21197
+ "extends",
21198
+ "false",
21199
+ "final",
21200
+ "finally",
21201
+ "float",
21202
+ "fn",
21203
+ "for",
21204
+ "foreach",
21205
+ "function",
21206
+ "global",
21207
+ "goto",
21208
+ "if",
21209
+ "implements",
21210
+ "include",
21211
+ "include_once",
21212
+ "instanceof",
21213
+ "insteadof",
21214
+ "int",
21215
+ "interface",
21216
+ "isset",
21217
+ "iterable",
21218
+ "list",
21219
+ "match",
21220
+ "mixed",
21221
+ "namespace",
21222
+ "never",
21223
+ "new",
21224
+ "null",
21225
+ "object",
21226
+ "or",
21227
+ "parent",
21228
+ "print",
21229
+ "private",
21230
+ "protected",
21231
+ "public",
21232
+ "readonly",
21233
+ "require",
21234
+ "require_once",
21235
+ "return",
21236
+ "self",
21237
+ "static",
21238
+ "string",
21239
+ "switch",
21240
+ "throw",
21241
+ "trait",
21242
+ "true",
21243
+ "try",
21244
+ "unset",
21245
+ "use",
21246
+ "var",
21247
+ "void",
21248
+ "while",
21249
+ "xor",
21250
+ "yield"
21251
+ ]);
21252
+ function phpFeatures(version) {
21253
+ const minor = Number.parseInt(version.split(".")[1], 10);
21254
+ return {
21255
+ enums: minor >= 1,
21256
+ readonlyProps: minor >= 1,
21257
+ readonlyClass: minor >= 2,
21258
+ typedConstants: minor >= 3,
21259
+ deprecatedAttribute: minor >= 4,
21260
+ noDiscard: minor >= 5,
21261
+ uriExtension: minor >= 5,
21262
+ pipeOperator: minor >= 5
21263
+ };
21264
+ }
21265
+ function phpClassName(name) {
21266
+ const safe = name.replace(/[^a-zA-Z0-9]+/g, " ").split(" ").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
21267
+ let result = safe || "Generated";
21268
+ if (/^[0-9]/.test(result)) {
21269
+ result = `_${result}`;
21270
+ }
21271
+ if (PHP_RESERVED.has(result.toLowerCase())) {
21272
+ result = `${result}Model`;
21273
+ }
21274
+ return result;
21275
+ }
21276
+ function phpPropertyName(name) {
21277
+ let result = name.replace(/[-_]([a-zA-Z])/g, (_, c) => c.toUpperCase());
21278
+ if (result.startsWith("_")) {
21279
+ result = result.slice(1);
21280
+ }
21281
+ result = result.charAt(0).toLowerCase() + result.slice(1);
21282
+ if (!/^[a-zA-Z_]/.test(result)) {
21283
+ result = `_${result}`;
21284
+ }
21285
+ return result;
21286
+ }
21287
+ function phpEnumCaseName(value) {
21288
+ let result = value.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/[^a-zA-Z0-9]+/g, " ").split(" ").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
21289
+ if (result.length === 0) return "Unknown";
21290
+ if (/^[0-9]/.test(result)) {
21291
+ result = `_${result}`;
21292
+ }
21293
+ if (result.toLowerCase() === "class") {
21294
+ result = `${result}_`;
21295
+ }
21296
+ return result;
21297
+ }
21298
+ function phpConstantName(value) {
21299
+ let result = value.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/[-\s]+/g, "_").replace(/[^a-zA-Z0-9_]/g, "").toUpperCase();
21300
+ if (result.length === 0) return "UNKNOWN";
21301
+ if (/^[0-9]/.test(result)) {
21302
+ result = `_${result}`;
21303
+ }
21304
+ if (result === "CLASS") {
21305
+ result = "CLASS_";
21306
+ }
21307
+ return result;
21308
+ }
21309
+ function enumBacking(values2) {
21310
+ if (values2.length === 0) return null;
21311
+ if (values2.every((v) => typeof v === "string")) return "string";
21312
+ if (values2.every((v) => typeof v === "number" && Number.isInteger(v))) return "int";
21313
+ return null;
21314
+ }
21315
+ function classifyPhpSchemas(schemas, features) {
21316
+ const kinds = /* @__PURE__ */ new Map();
21317
+ for (const [name, schema] of schemas) {
21318
+ kinds.set(name, classifySchema(schema, features, schemas));
21319
+ }
21320
+ return kinds;
21321
+ }
21322
+ function classifySchema(schema, features, lookup) {
21323
+ if (schema.enum) {
21324
+ const backing = enumBacking(schema.enum);
21325
+ if (backing) {
21326
+ return features.enums ? { kind: "enum", backing } : { kind: "constants", backing };
21327
+ }
21328
+ return { kind: "alias" };
21329
+ }
21330
+ if (schema.oneOf || schema.anyOf) {
21331
+ return { kind: "alias" };
21332
+ }
21333
+ if (schema.allOf) {
21334
+ return hasMergedProperties(schema, lookup) ? { kind: "class" } : { kind: "alias" };
21335
+ }
21336
+ if (schema.properties && schema.properties.size > 0) {
21337
+ return { kind: "class" };
21338
+ }
21339
+ return { kind: "alias" };
21340
+ }
21341
+ function hasMergedProperties(schema, lookup, visitedRefs = /* @__PURE__ */ new Set()) {
21342
+ if (schema.refName && lookup.has(schema.refName)) {
21343
+ if (visitedRefs.has(schema.refName)) return false;
21344
+ visitedRefs.add(schema.refName);
21345
+ return hasMergedProperties(lookup.get(schema.refName), lookup, visitedRefs);
21346
+ }
21347
+ if (schema.properties && schema.properties.size > 0) return true;
21348
+ if (schema.allOf) {
21349
+ return schema.allOf.some((sub) => hasMergedProperties(sub, lookup, new Set(visitedRefs)));
21350
+ }
21351
+ return false;
21352
+ }
21353
+ var MIXED = { native: "mixed", doc: "mixed", deserialize: { kind: "json" } };
21354
+ function primitive(native) {
21355
+ return { native, doc: native, deserialize: { kind: "json" } };
21356
+ }
21357
+ function maybeNullable3(t, schema) {
21358
+ if (!schema.nullable) return t;
21359
+ return nullablePhpType(t);
21360
+ }
21361
+ function nullablePhpType(t) {
21362
+ return {
21363
+ native: t.native === "mixed" || t.native.startsWith("?") ? t.native : `?${t.native}`,
21364
+ doc: t.doc === "mixed" || t.doc.endsWith("|null") ? t.doc : `${t.doc}|null`,
21365
+ deserialize: t.deserialize
21366
+ };
21367
+ }
21368
+ function namedType(name, schema, pctx, visited) {
21369
+ const kind = pctx.kinds.get(name) ?? { kind: "alias" };
21370
+ switch (kind.kind) {
21371
+ case "class": {
21372
+ const className = pctx.classPrefix + phpClassName(name);
21373
+ return maybeNullable3({ native: className, doc: className, deserialize: { kind: "class", name: className } }, schema);
21374
+ }
21375
+ case "enum": {
21376
+ const className = pctx.classPrefix + phpClassName(name);
21377
+ return maybeNullable3({ native: className, doc: className, deserialize: { kind: "enum", name: className } }, schema);
21378
+ }
21379
+ case "constants":
21380
+ return maybeNullable3(primitive(kind.backing === "int" ? "int" : "string"), schema);
21381
+ case "alias": {
21382
+ if (visited.has(name)) return MIXED;
21383
+ visited.add(name);
21384
+ const target = pctx.conv.schemaLookup?.get(name);
21385
+ if (!target) return maybeNullable3(MIXED, schema);
21386
+ return maybeNullable3(resolvePhpType(target, pctx, visited), schema);
21387
+ }
21388
+ }
21389
+ }
21390
+ function schemaToPhpType(schema, pctx) {
21391
+ return resolvePhpType(schema, pctx, /* @__PURE__ */ new Set());
21392
+ }
21393
+ function resolvePhpType(schema, pctx, visited) {
21394
+ if (schema.isCircular && schema.refName) {
21395
+ const kind = pctx.kinds.get(schema.refName);
21396
+ if (kind && (kind.kind === "class" || kind.kind === "enum")) {
21397
+ const className = pctx.classPrefix + phpClassName(schema.refName);
21398
+ return nullablePhpType({
21399
+ native: className,
21400
+ doc: className,
21401
+ deserialize: kind.kind === "class" ? { kind: "class", name: className } : { kind: "enum", name: className }
21402
+ });
21403
+ }
21404
+ return MIXED;
21405
+ }
21406
+ if (schema.generatedName && pctx.conv.namedSchemas.has(schema.generatedName)) {
21407
+ return namedType(schema.generatedName, schema, pctx, visited);
21408
+ }
21409
+ if (schema.refName && pctx.conv.namedSchemas.has(schema.refName)) {
21410
+ return namedType(schema.refName, schema, pctx, visited);
21411
+ }
21412
+ if (schema.allOf) return resolveAllOfType3(schema, pctx, visited);
21413
+ if (schema.oneOf) return resolveOneOfType3(schema, pctx, visited);
21414
+ if (schema.anyOf) return resolveAnyOfType3(schema, pctx, visited);
21415
+ if (schema.enum) return resolveEnumType3(schema);
21416
+ switch (normalizeType(schema)) {
21417
+ case "string":
21418
+ return maybeNullable3(primitive("string"), schema);
21419
+ case "number":
21420
+ return maybeNullable3(primitive("float"), schema);
21421
+ case "integer":
21422
+ return maybeNullable3(primitive("int"), schema);
21423
+ case "boolean":
21424
+ return maybeNullable3(primitive("bool"), schema);
21425
+ case "null":
21426
+ return { native: "mixed", doc: "null", deserialize: { kind: "json" } };
21427
+ case "array":
21428
+ return resolveArrayType3(schema, pctx, visited);
21429
+ case "object":
21430
+ return resolveObjectType3(schema, pctx, visited);
21431
+ default:
21432
+ return maybeNullable3(MIXED, schema);
21433
+ }
21434
+ }
21435
+ function resolveArrayType3(schema, pctx, visited) {
21436
+ const itemType = schema.items ? resolvePhpType(schema.items, pctx, visited) : MIXED;
21437
+ const itemDoc = itemType.doc.includes("|") || itemType.doc.includes("<") ? `array<${itemType.doc}>` : `${itemType.doc}[]`;
21438
+ const deserialize = itemType.deserialize.kind === "class" ? { kind: "classArray", name: itemType.deserialize.name } : { kind: "json" };
21439
+ return maybeNullable3({ native: "array", doc: itemDoc, deserialize }, schema);
21440
+ }
21441
+ function resolveObjectType3(schema, pctx, visited) {
21442
+ if (!schema.properties || schema.properties.size === 0) {
21443
+ if (typeof schema.additionalProperties === "object") {
21444
+ const valueType = resolvePhpType(schema.additionalProperties, pctx, visited);
21445
+ return maybeNullable3({ native: "array", doc: `array<string, ${valueType.doc}>`, deserialize: { kind: "json" } }, schema);
21446
+ }
21447
+ return maybeNullable3({ native: "array", doc: "array<string, mixed>", deserialize: { kind: "json" } }, schema);
21448
+ }
21449
+ if (schema.generatedName && pctx.conv.namedSchemas.has(schema.generatedName)) {
21450
+ return namedType(schema.generatedName, schema, pctx, visited);
21451
+ }
21452
+ return maybeNullable3(MIXED, schema);
21453
+ }
21454
+ function resolveAllOfType3(schema, pctx, visited) {
21455
+ const schemas = schema.allOf;
21456
+ if (schemas.length === 1) return resolvePhpType(schemas[0], pctx, visited);
21457
+ if (schemas[0].refName && pctx.conv.namedSchemas.has(schemas[0].refName)) {
21458
+ return namedType(schemas[0].refName, schema, pctx, visited);
21459
+ }
21460
+ return maybeNullable3(MIXED, schema);
21461
+ }
21462
+ function resolveOneOfType3(schema, pctx, visited) {
21463
+ const schemas = schema.oneOf;
21464
+ if (schemas.length === 1) return resolvePhpType(schemas[0], pctx, visited);
21465
+ return maybeNullable3(MIXED, schema);
21466
+ }
21467
+ function resolveAnyOfType3(schema, pctx, visited) {
21468
+ const schemas = schema.anyOf;
21469
+ const nonNull = schemas.filter((s) => s.type !== "null");
21470
+ const hasNull = schemas.some((s) => s.type === "null");
21471
+ if (hasNull && nonNull.length === 1) {
21472
+ return nullablePhpType(resolvePhpType(nonNull[0], pctx, visited));
21473
+ }
21474
+ if (schemas.length === 1) return resolvePhpType(schemas[0], pctx, visited);
21475
+ return maybeNullable3(MIXED, schema);
21476
+ }
21477
+ function resolveEnumType3(schema) {
21478
+ const backing = enumBacking(schema.enum);
21479
+ if (backing === "int") return primitive("int");
21480
+ if (backing === "string") return primitive("string");
21481
+ return MIXED;
21482
+ }
21483
+ function phpStringify(schema, expr, pctx) {
21484
+ const type = schemaToPhpType(schema, pctx);
21485
+ if (type.deserialize.kind === "enum") {
21486
+ return `${expr}->value`;
21487
+ }
21488
+ if (type.native.replace("?", "") === "bool") {
21489
+ return `${expr} ? 'true' : 'false'`;
21490
+ }
21491
+ return expr;
21492
+ }
21493
+ function generatePhpModel(name, schema, pctx) {
21494
+ const kind = pctx.kinds.get(name);
21495
+ if (!kind || kind.kind === "alias") return null;
21496
+ if (kind.kind === "enum") {
21497
+ return generatePhpEnum(name, schema, kind.backing);
21498
+ }
21499
+ if (kind.kind === "constants") {
21500
+ return generatePhpConstantsClass(name, schema, kind.backing);
21501
+ }
21502
+ if (schema.allOf) {
21503
+ return generatePhpAllOfClass(name, schema, pctx);
21504
+ }
21505
+ if (schema.properties && schema.properties.size > 0) {
21506
+ return generatePhpObjectClass(name, schema, pctx);
21507
+ }
21508
+ return null;
21509
+ }
21510
+ function generatePhpEnum(name, schema, backing) {
21511
+ const lines = [];
21512
+ lines.push(`enum ${phpClassName(name)}: ${backing}`);
21513
+ lines.push("{");
21514
+ const seen = /* @__PURE__ */ new Set();
21515
+ for (const v of schema.enum) {
21516
+ let caseName = phpEnumCaseName(String(v));
21517
+ while (seen.has(caseName)) caseName = `${caseName}_`;
21518
+ seen.add(caseName);
21519
+ const literal = backing === "string" ? quotePhpString(String(v)) : String(v);
21520
+ lines.push(` case ${caseName} = ${literal};`);
21521
+ }
21522
+ lines.push("}");
21523
+ return { code: lines.join("\n"), uses: [] };
21524
+ }
21525
+ function generatePhpConstantsClass(name, schema, backing) {
21526
+ const lines = [];
21527
+ lines.push(`final class ${phpClassName(name)}`);
21528
+ lines.push("{");
21529
+ const seen = /* @__PURE__ */ new Set();
21530
+ for (const v of schema.enum) {
21531
+ let constName = phpConstantName(String(v));
21532
+ while (seen.has(constName)) constName = `${constName}_`;
21533
+ seen.add(constName);
21534
+ const literal = backing === "string" ? quotePhpString(String(v)) : String(v);
21535
+ lines.push(` public const ${constName} = ${literal};`);
21536
+ }
21537
+ lines.push("}");
21538
+ return { code: lines.join("\n"), uses: [] };
21539
+ }
21540
+ function generatePhpObjectClass(name, schema, pctx) {
21541
+ const properties = schema.properties;
21542
+ const requiredFields = new Set(schema.required ?? []);
21543
+ const { features } = pctx;
21544
+ const uses = /* @__PURE__ */ new Set();
21545
+ const entries = [];
21546
+ for (const [propName, propSchema] of properties) {
21547
+ entries.push({
21548
+ wireName: propName,
21549
+ safeName: phpPropertyName(propName),
21550
+ type: schemaToPhpType(propSchema, pctx),
21551
+ required: requiredFields.has(propName)
21552
+ });
21553
+ }
21554
+ const ordered = [...entries.filter((e) => e.required), ...entries.filter((e) => !e.required)];
21555
+ const lines = [];
21556
+ const classKeywords = features.readonlyClass ? "final readonly class" : "final class";
21557
+ lines.push(`${classKeywords} ${phpClassName(name)}`);
21558
+ lines.push("{");
21559
+ const docParams = ordered.filter((e) => e.type.doc.includes("[]") || e.type.doc.includes("array<"));
21560
+ if (docParams.length > 0) {
21561
+ lines.push(" /**");
21562
+ for (const e of docParams) {
21563
+ const doc = e.required ? e.type.doc : appendNullDoc(e.type.doc);
21564
+ lines.push(` * @param ${doc} $${e.safeName}`);
21565
+ }
21566
+ lines.push(" */");
21567
+ }
21568
+ lines.push(" public function __construct(");
21569
+ const propKeywords = features.readonlyClass ? "public" : features.readonlyProps ? "public readonly" : "public";
21570
+ for (const e of ordered) {
21571
+ let native = e.type.native;
21572
+ let suffix = "";
21573
+ if (!e.required) {
21574
+ if (native !== "mixed" && !native.startsWith("?")) {
21575
+ native = `?${native}`;
21576
+ }
21577
+ suffix = " = null";
21578
+ }
21579
+ if (e.safeName !== e.wireName) {
21580
+ lines.push(` #[SerializedName(${quotePhpString(e.wireName)})]`);
21581
+ uses.add(serializedNameImport(features));
21582
+ }
21583
+ lines.push(` ${propKeywords} ${native} $${e.safeName}${suffix},`);
21584
+ }
21585
+ lines.push(" ) {");
21586
+ lines.push(" }");
21587
+ lines.push("}");
21588
+ return { code: lines.join("\n"), uses: [...uses] };
21589
+ }
21590
+ function appendNullDoc(doc) {
21591
+ return doc.endsWith("|null") ? doc : `${doc}|null`;
21592
+ }
21593
+ function serializedNameImport(features) {
21594
+ return features.enums ? "Symfony\\Component\\Serializer\\Attribute\\SerializedName" : "Symfony\\Component\\Serializer\\Annotation\\SerializedName";
21595
+ }
21596
+ function generatePhpAllOfClass(name, schema, pctx) {
21597
+ const schemas = schema.allOf;
21598
+ const allProps = /* @__PURE__ */ new Map();
21599
+ for (const s of schemas) {
21600
+ collectPhpProperties(s, pctx, allProps);
21601
+ }
21602
+ if (allProps.size === 0) {
21603
+ return null;
21604
+ }
21605
+ const mergedSchema = {
21606
+ type: "object",
21607
+ properties: /* @__PURE__ */ new Map(),
21608
+ required: []
21609
+ };
21610
+ for (const [propName, prop] of allProps) {
21611
+ mergedSchema.properties.set(propName, prop.schema);
21612
+ if (prop.required) {
21613
+ mergedSchema.required.push(propName);
21614
+ }
21615
+ }
21616
+ return generatePhpObjectClass(name, mergedSchema, pctx);
21617
+ }
21618
+ function collectPhpProperties(schema, pctx, allProps, visitedRefs = /* @__PURE__ */ new Set()) {
21619
+ if (schema.refName && pctx.conv.schemaLookup?.has(schema.refName)) {
21620
+ if (visitedRefs.has(schema.refName)) return;
21621
+ visitedRefs.add(schema.refName);
21622
+ collectPhpProperties(pctx.conv.schemaLookup.get(schema.refName), pctx, allProps, visitedRefs);
21623
+ return;
21624
+ }
21625
+ if (schema.allOf) {
21626
+ for (const subSchema of schema.allOf) {
21627
+ collectPhpProperties(subSchema, pctx, allProps, new Set(visitedRefs));
21628
+ }
21629
+ }
21630
+ if (!schema.properties) return;
21631
+ const required = new Set(schema.required ?? []);
21632
+ for (const [propName, propSchema] of schema.properties) {
21633
+ allProps.set(propName, {
21634
+ schema: propSchema,
21635
+ required: required.has(propName) || (allProps.get(propName)?.required ?? false)
21636
+ });
21637
+ }
21638
+ }
21639
+ function quotePhpString(value) {
21640
+ return `'${value.replace(/\\/g, "\\\\").replace(/'/g, "\\'")}'`;
21641
+ }
21642
+
21643
+ // src/modes/shared/response-cases-php.ts
21644
+ function getPhpResponseDocType(op, pctx) {
21645
+ const shapes = collectPhpSuccessShapes(op, pctx);
21646
+ if (shapes.length === 0) return "null";
21647
+ const distinctDocs = new Set(shapes.map((shape) => shapeDocType(shape)));
21648
+ return distinctDocs.size === 1 ? shapeDocType(shapes[0]) : "mixed";
21649
+ }
21650
+ function generatePhpResponseCases(op, pctx) {
21651
+ const cases = [];
21652
+ for (const [statusCode, response] of orderResponseEntries(op.responses)) {
21653
+ const matcher = phpStatusMatcher(statusCode);
21654
+ if (!matcher) continue;
21655
+ if (!isSuccessStatus(statusCode)) {
21656
+ cases.push(`${matcher} => throw new ApiException($statusCode, $content, $headers, $requestUrl),`);
21657
+ continue;
21658
+ }
21659
+ const shape = getPhpResponseShape(response, pctx);
21660
+ cases.push(`${matcher} => new ApiResponse(${phpDataExpr(shape)}, $statusCode, $headers, $requestUrl),`);
21661
+ }
21662
+ cases.push("default => throw new ApiException($statusCode, $content, $headers, $requestUrl),");
21663
+ return cases;
21664
+ }
21665
+ function phpDataExpr(shape) {
21666
+ if (shape.mode === "empty") return "null";
21667
+ if (shape.mode === "text") return "$content";
21668
+ const target = shape.type.deserialize;
21669
+ switch (target.kind) {
21670
+ case "class":
21671
+ case "enum":
21672
+ return `$this->serializer->deserialize($content, ${target.name}::class, 'json')`;
21673
+ case "classArray":
21674
+ return `$this->serializer->deserialize($content, ${target.name}::class . '[]', 'json')`;
21675
+ case "json":
21676
+ return "json_decode($content, true, 512, \\JSON_THROW_ON_ERROR)";
21677
+ }
21678
+ }
21679
+ function shapeDocType(shape) {
21680
+ if (shape.mode === "empty") return "null";
21681
+ if (shape.mode === "text") return "string";
21682
+ return shape.type.doc;
21683
+ }
21684
+ function collectPhpSuccessShapes(op, pctx) {
21685
+ const shapes = [];
21686
+ for (const [statusCode, response] of op.responses) {
21687
+ if (!isSuccessStatus(statusCode)) continue;
21688
+ shapes.push(getPhpResponseShape(response, pctx));
21689
+ }
21690
+ return shapes;
21691
+ }
21692
+ function getPhpResponseShape(response, pctx) {
21693
+ if (!response.hasBody) {
21694
+ return { type: null, mode: "empty" };
21695
+ }
21696
+ const jsonSchema = response.contentTypes.get("application/json");
21697
+ if (jsonSchema) {
21698
+ return { type: schemaToPhpType(jsonSchema, pctx), mode: "json" };
21699
+ }
21700
+ return { type: null, mode: "text" };
21701
+ }
21702
+ function phpStatusMatcher(statusCode) {
21703
+ if (statusCode === "default") return null;
21704
+ const wildcardMatch = statusCode.match(/^([1-5])XX$/i);
21705
+ if (wildcardMatch) {
21706
+ const prefix = Number.parseInt(wildcardMatch[1], 10);
21707
+ const start = prefix * 100;
21708
+ return `$statusCode >= ${start} && $statusCode < ${start + 100}`;
21709
+ }
21710
+ const code = Number.parseInt(statusCode, 10);
21711
+ return Number.isNaN(code) ? null : `$statusCode === ${code}`;
21712
+ }
21713
+
21714
+ // src/modes/php/helpers.ts
21715
+ var RESERVED_LOCALS = /* @__PURE__ */ new Set(["response", "statusCode", "content", "headers", "requestUrl", "query", "options", "body", "url"]);
21716
+ function phpParamName(name) {
21717
+ const safe = phpPropertyName(name);
21718
+ return RESERVED_LOCALS.has(safe) ? `${safe}Param` : safe;
21719
+ }
21720
+ function phpFile(namespace, uses, code) {
21721
+ const lines = ["<?php", "", "declare(strict_types=1);", "", `namespace ${namespace};`, ""];
21722
+ const sorted = [...new Set(uses)].sort();
21723
+ for (const use of sorted) {
21724
+ lines.push(`use ${use};`);
21725
+ }
21726
+ if (sorted.length > 0) {
21727
+ lines.push("");
21728
+ }
21729
+ lines.push(code);
21730
+ lines.push("");
21731
+ return lines.join("\n");
21732
+ }
21733
+ function generatePhpOperation(op, pctx) {
21734
+ const { features } = pctx;
21735
+ const entry = (p) => ({ param: p, safeName: phpParamName(p.name), type: schemaToPhpType(p.schema, pctx) });
21736
+ const pathParams = op.parameters.filter((p) => p.in === "path").map(entry);
21737
+ const queryParams = op.parameters.filter((p) => p.in === "query").map(entry);
21738
+ const headerParams = op.parameters.filter((p) => p.in === "header").map(entry);
21739
+ const hasBody = op.type === "mutation" && op.requestBody !== null;
21740
+ const bodySchema = hasBody ? op.requestBody.contentTypes.get(op.requestBody.primaryContentType) ?? null : null;
21741
+ const bodyType = bodySchema ? schemaToPhpType(bodySchema, pctx) : null;
21742
+ const bodyRequired = hasBody && op.requestBody.required;
21743
+ const sig = [];
21744
+ for (const e of pathParams) {
21745
+ sig.push({ decl: `${e.type.native} $${e.safeName}`, docType: docTypeIfUseful(e.type), safeName: e.safeName, required: true });
21746
+ }
21747
+ if (bodyType) {
21748
+ if (bodyRequired) {
21749
+ sig.push({ decl: `${bodyType.native} $body`, docType: docTypeIfUseful(bodyType), safeName: "body", required: true });
21750
+ } else {
21751
+ sig.push({
21752
+ decl: `${optionalNative(bodyType)} $body = null`,
21753
+ docType: nullableDoc(docTypeIfUseful(bodyType)),
21754
+ safeName: "body",
21755
+ required: false
21756
+ });
21757
+ }
21758
+ }
21759
+ for (const e of [...queryParams, ...headerParams]) {
21760
+ if (e.param.required) {
21761
+ sig.push({ decl: `${e.type.native} $${e.safeName}`, docType: docTypeIfUseful(e.type), safeName: e.safeName, required: true });
21762
+ } else {
21763
+ sig.push({
21764
+ decl: `${optionalNative(e.type)} $${e.safeName} = null`,
21765
+ docType: nullableDoc(docTypeIfUseful(e.type)),
21766
+ safeName: e.safeName,
21767
+ required: false
21768
+ });
21769
+ }
21770
+ }
21771
+ const ordered = [...sig.filter((s) => s.required), ...sig.filter((s) => !s.required)];
21772
+ const responseDocType = getPhpResponseDocType(op, pctx);
21773
+ const lines = [];
21774
+ lines.push(" /**");
21775
+ if (op.description) {
21776
+ lines.push(` * ${op.description.replace(/\n/g, " ").trim()}`);
21777
+ lines.push(" *");
21778
+ }
21779
+ for (const s of ordered) {
21780
+ if (s.docType) {
21781
+ lines.push(` * @param ${s.docType} $${s.safeName}`);
21782
+ }
21783
+ }
21784
+ lines.push(` * @return ApiResponse<${responseDocType}>`);
21785
+ lines.push(" * @throws ApiException");
21786
+ if (op.deprecated && !features.deprecatedAttribute) {
21787
+ lines.push(" * @deprecated Deprecated in the OpenAPI specification");
21788
+ }
21789
+ lines.push(" */");
21790
+ if (op.deprecated && features.deprecatedAttribute) {
21791
+ lines.push(" #[\\Deprecated(message: 'Deprecated in the OpenAPI specification')]");
21792
+ }
21793
+ if (features.noDiscard) {
21794
+ lines.push(" #[\\NoDiscard]");
21795
+ }
21796
+ lines.push(` public function ${op.operationId}(${ordered.map((s) => s.decl).join(", ")}): ApiResponse`);
21797
+ lines.push(" {");
21798
+ if (queryParams.length > 0) {
21799
+ lines.push(" $query = [];");
21800
+ for (const e of queryParams) {
21801
+ const value = phpStringify(e.param.schema, `$${e.safeName}`, pctx);
21802
+ if (e.param.required) {
21803
+ lines.push(` $query[${quotePhpString(e.param.name)}] = ${value};`);
21804
+ } else {
21805
+ lines.push(` if ($${e.safeName} !== null) {`);
21806
+ lines.push(` $query[${quotePhpString(e.param.name)}] = ${value};`);
21807
+ lines.push(" }");
21808
+ }
21809
+ }
21810
+ lines.push("");
21811
+ }
21812
+ if (headerParams.length > 0) {
21813
+ lines.push(" $headerValues = [];");
21814
+ for (const e of headerParams) {
21815
+ const value = phpStringify(e.param.schema, `$${e.safeName}`, pctx);
21816
+ if (e.param.required) {
21817
+ lines.push(` $headerValues[${quotePhpString(e.param.name)}] = ${value};`);
21818
+ } else {
21819
+ lines.push(` if ($${e.safeName} !== null) {`);
21820
+ lines.push(` $headerValues[${quotePhpString(e.param.name)}] = ${value};`);
21821
+ lines.push(" }");
21822
+ }
21823
+ }
21824
+ lines.push("");
21825
+ }
21826
+ const pathExpr = phpPathExpr(op.path, pathParams, pctx);
21827
+ if (features.uriExtension) {
21828
+ lines.push(` $url = new \\Uri\\Rfc3986\\Uri($this->baseUrl . ${pathExpr})->toString();`);
21829
+ } else {
21830
+ lines.push(` $url = $this->baseUrl . ${pathExpr};`);
21831
+ }
21832
+ const hasOptions = queryParams.length > 0 || headerParams.length > 0 || bodyType !== null;
21833
+ if (hasOptions) {
21834
+ lines.push(" $options = [];");
21835
+ if (queryParams.length > 0) {
21836
+ if (queryParams.some((e) => e.param.required)) {
21837
+ lines.push(" $options['query'] = $query;");
21838
+ } else {
21839
+ lines.push(" if ($query !== []) {");
21840
+ lines.push(" $options['query'] = $query;");
21841
+ lines.push(" }");
21842
+ }
21843
+ }
21844
+ if (headerParams.length > 0) {
21845
+ if (headerParams.some((e) => e.param.required)) {
21846
+ lines.push(" $options['headers'] = $headerValues;");
21847
+ } else {
21848
+ lines.push(" if ($headerValues !== []) {");
21849
+ lines.push(" $options['headers'] = $headerValues;");
21850
+ lines.push(" }");
21851
+ }
21852
+ }
21853
+ if (bodyType) {
21854
+ const body = phpBodyLines(bodyType);
21855
+ if (bodyRequired) {
21856
+ lines.push(...body.map((l) => ` ${l}`));
21857
+ } else {
21858
+ lines.push(" if ($body !== null) {");
21859
+ lines.push(...body.map((l) => ` ${l}`));
21860
+ lines.push(" }");
21861
+ }
21862
+ }
21863
+ lines.push("");
21864
+ lines.push(` $response = $this->client->request(${quotePhpString(op.method.toUpperCase())}, $url, $options);`);
21865
+ } else {
21866
+ lines.push("");
21867
+ lines.push(` $response = $this->client->request(${quotePhpString(op.method.toUpperCase())}, $url);`);
21868
+ }
21869
+ lines.push("");
21870
+ lines.push(" $statusCode = $response->getStatusCode();");
21871
+ lines.push(" $content = $response->getContent(false);");
21872
+ lines.push(" $headers = $response->getHeaders(false);");
21873
+ lines.push(" $requestUrl = (string) $response->getInfo('url');");
21874
+ lines.push("");
21875
+ lines.push(" return match (true) {");
21876
+ for (const c of generatePhpResponseCases(op, pctx)) {
21877
+ lines.push(` ${c}`);
21878
+ }
21879
+ lines.push(" };");
21880
+ lines.push(" }");
21881
+ return lines.join("\n");
21882
+ }
21883
+ function optionalNative(type) {
21884
+ if (type.native === "mixed" || type.native.startsWith("?")) return type.native;
21885
+ return `?${type.native}`;
21886
+ }
21887
+ function docTypeIfUseful(type) {
21888
+ return type.doc.includes("[]") || type.doc.includes("array<") ? type.doc : null;
21889
+ }
21890
+ function nullableDoc(doc) {
21891
+ if (doc === null) return null;
21892
+ return doc.endsWith("|null") ? doc : `${doc}|null`;
21893
+ }
21894
+ function phpBodyLines(bodyType) {
21895
+ const bare = bodyType.native.replace("?", "");
21896
+ const isModelLike = bodyType.deserialize.kind !== "json" || bare === "array";
21897
+ if (!isModelLike) {
21898
+ return ["$options['json'] = $body;"];
21899
+ }
21900
+ return [
21901
+ "$options['headers']['Content-Type'] = 'application/json';",
21902
+ "$options['body'] = $this->serializer->serialize($body, 'json', [AbstractObjectNormalizer::SKIP_NULL_VALUES => true]);"
21903
+ ];
21904
+ }
21905
+ function phpPathExpr(path, pathParams, pctx) {
21906
+ const parts = [];
21907
+ const regex = /\{([^}]+)\}/g;
21908
+ let lastIndex = 0;
21909
+ let match;
21910
+ while ((match = regex.exec(path)) !== null) {
21911
+ const literal = path.slice(lastIndex, match.index);
21912
+ if (literal) parts.push(quotePhpString(literal));
21913
+ parts.push(pathParamExpr(match[1], pathParams, pctx));
21914
+ lastIndex = regex.lastIndex;
21915
+ }
21916
+ const tail = path.slice(lastIndex);
21917
+ if (tail) parts.push(quotePhpString(tail));
21918
+ return parts.length > 0 ? parts.join(" . ") : quotePhpString(path);
21919
+ }
21920
+ function pathParamExpr(paramName, pathParams, pctx) {
21921
+ const entry = pathParams.find((e) => e.param.name === paramName);
21922
+ if (!entry) return quotePhpString(`{${paramName}}`);
21923
+ const stringified = phpStringify(entry.param.schema, `$${entry.safeName}`, pctx);
21924
+ const needsCast = stringified === `$${entry.safeName}` && entry.type.native.replace("?", "") !== "string";
21925
+ return `rawurlencode(${needsCast ? `(string) $${entry.safeName}` : stringified})`;
21926
+ }
21927
+
21928
+ // src/modes/php/query.ts
21929
+ function generatePhpQuery(op, pctx) {
21930
+ return generatePhpOperation(op, pctx);
21931
+ }
21932
+
21933
+ // src/modes/php/mutation.ts
21934
+ function generatePhpMutation(op, pctx) {
21935
+ return generatePhpOperation(op, pctx);
21936
+ }
21937
+
21938
+ // src/modes/php/runtime.ts
21939
+ function generatePhpRuntimeFiles(php, features) {
21940
+ const files = /* @__PURE__ */ new Map();
21941
+ files.set("ApiResponse.php", phpFile(php.namespace, [], apiResponseClass(features)));
21942
+ files.set("ApiException.php", phpFile(php.namespace, [], apiExceptionClass(features)));
21943
+ files.set("SerializerFactory.php", phpFile(php.namespace, serializerFactoryUses(features), serializerFactoryClass(features)));
21944
+ return files;
21945
+ }
21946
+ function apiResponseClass(features) {
21947
+ const classKeywords = features.readonlyClass ? "final readonly class" : "final class";
21948
+ const prop = features.readonlyClass ? "public" : features.readonlyProps ? "public readonly" : "public";
21949
+ return [
21950
+ "/**",
21951
+ " * @template T",
21952
+ " */",
21953
+ `${classKeywords} ApiResponse`,
21954
+ "{",
21955
+ " /**",
21956
+ " * @param T $data",
21957
+ " * @param array<string, string[]> $headers Response headers (keys are lower-cased by Symfony HttpClient)",
21958
+ " */",
21959
+ " public function __construct(",
21960
+ ` ${prop} mixed $data,`,
21961
+ ` ${prop} int $status,`,
21962
+ ` ${prop} array $headers,`,
21963
+ ` ${prop} string $url,`,
21964
+ " ) {",
21965
+ " }",
21966
+ "}"
21967
+ ].join("\n");
21968
+ }
21969
+ function apiExceptionClass(features) {
21970
+ const prop = features.readonlyProps ? "public readonly" : "public";
21971
+ return [
21972
+ "class ApiException extends \\RuntimeException",
21973
+ "{",
21974
+ " /**",
21975
+ " * @param array<string, string[]> $responseHeaders",
21976
+ " */",
21977
+ " public function __construct(",
21978
+ ` ${prop} int $statusCode,`,
21979
+ ` ${prop} ?string $body,`,
21980
+ ` ${prop} array $responseHeaders,`,
21981
+ ` ${prop} string $url,`,
21982
+ " ) {",
21983
+ " parent::__construct(sprintf('API error %d (%s)', $statusCode, $url));",
21984
+ " }",
21985
+ "}"
21986
+ ].join("\n");
21987
+ }
21988
+ function serializerFactoryUses(features) {
21989
+ const uses = [
21990
+ "Symfony\\Component\\PropertyInfo\\Extractor\\PhpDocExtractor",
21991
+ "Symfony\\Component\\PropertyInfo\\Extractor\\ReflectionExtractor",
21992
+ "Symfony\\Component\\PropertyInfo\\PropertyInfoExtractor",
21993
+ "Symfony\\Component\\Serializer\\Encoder\\JsonEncoder",
21994
+ "Symfony\\Component\\Serializer\\Mapping\\Factory\\ClassMetadataFactory",
21995
+ "Symfony\\Component\\Serializer\\NameConverter\\MetadataAwareNameConverter",
21996
+ "Symfony\\Component\\Serializer\\Normalizer\\ArrayDenormalizer",
21997
+ "Symfony\\Component\\Serializer\\Normalizer\\ObjectNormalizer",
21998
+ "Symfony\\Component\\Serializer\\Serializer"
21999
+ ];
22000
+ if (features.enums) {
22001
+ uses.push("Symfony\\Component\\Serializer\\Mapping\\Loader\\AttributeLoader");
22002
+ uses.push("Symfony\\Component\\Serializer\\Normalizer\\BackedEnumNormalizer");
22003
+ } else {
22004
+ uses.push("Symfony\\Component\\Serializer\\Mapping\\Loader\\AnnotationLoader");
22005
+ }
22006
+ return uses;
22007
+ }
22008
+ function serializerFactoryClass(features) {
22009
+ const loader = features.enums ? "AttributeLoader" : "AnnotationLoader";
22010
+ const normalizers = features.enums ? [
22011
+ " new BackedEnumNormalizer(),",
22012
+ " new ArrayDenormalizer(),",
22013
+ " new ObjectNormalizer($classMetadataFactory, $nameConverter, null, $propertyInfo),"
22014
+ ] : [" new ArrayDenormalizer(),", " new ObjectNormalizer($classMetadataFactory, $nameConverter, null, $propertyInfo),"];
22015
+ return [
22016
+ "final class SerializerFactory",
22017
+ "{",
22018
+ " public static function create(): Serializer",
22019
+ " {",
22020
+ ` $classMetadataFactory = new ClassMetadataFactory(new ${loader}());`,
22021
+ " $nameConverter = new MetadataAwareNameConverter($classMetadataFactory);",
22022
+ " $propertyInfo = new PropertyInfoExtractor(",
22023
+ " [],",
22024
+ " [new PhpDocExtractor(), new ReflectionExtractor()],",
22025
+ " );",
22026
+ "",
22027
+ " return new Serializer(",
22028
+ " [",
22029
+ ...normalizers,
22030
+ " ],",
22031
+ " [new JsonEncoder()],",
22032
+ " );",
22033
+ " }",
22034
+ "}"
22035
+ ].join("\n");
22036
+ }
22037
+
22038
+ // src/modes/php/index.ts
22039
+ var phpMode = {
22040
+ name: "php",
22041
+ nativeTarget: true,
22042
+ generateFiles(ctx) {
22043
+ const php = ctx.php ?? { namespace: ctx.clientName, phpVersion: "8.1" };
22044
+ const features = phpFeatures(php.phpVersion);
22045
+ const conv = createConversionContext(new Set(ctx.document.schemas.keys()), void 0, ctx.document.schemas);
22046
+ const kinds = classifyPhpSchemas(ctx.document.schemas, features);
22047
+ const modelCtx = { conv, php, features, kinds, classPrefix: "" };
22048
+ const clientCtx = { ...modelCtx, classPrefix: "Model\\" };
22049
+ const files = /* @__PURE__ */ new Map();
22050
+ for (const [name, schema] of ctx.document.schemas) {
22051
+ const chunk = generatePhpModel(name, schema, modelCtx);
22052
+ if (!chunk) continue;
22053
+ files.set(`Model/${phpClassName(name)}.php`, phpFile(`${php.namespace}\\Model`, chunk.uses, chunk.code));
22054
+ }
22055
+ for (const [relPath, content] of generatePhpRuntimeFiles(php, features)) {
22056
+ files.set(relPath, content);
22057
+ }
22058
+ files.set(`${ctx.clientName}.php`, generatePhpClientFile(ctx, clientCtx));
22059
+ return files;
22060
+ }
22061
+ };
22062
+ function generatePhpClientFile(ctx, pctx) {
22063
+ const { clientName } = ctx;
22064
+ const { features, php } = pctx;
22065
+ const servers = ctx.document.servers;
22066
+ const defaultServer = servers[0];
22067
+ const lines = [];
22068
+ lines.push(`final class ${clientName}`);
22069
+ lines.push("{");
22070
+ if (defaultServer) {
22071
+ if (features.typedConstants) {
22072
+ lines.push(` public const string DEFAULT_BASE_URL = ${quotePhpString(defaultServer.url)};`);
22073
+ } else {
22074
+ lines.push(` public const DEFAULT_BASE_URL = ${quotePhpString(defaultServer.url)};`);
22075
+ }
22076
+ lines.push("");
22077
+ lines.push(" /** @var array<string, string> */");
22078
+ const constKeyword = features.typedConstants ? "public const array" : "public const";
22079
+ lines.push(` ${constKeyword} SERVERS = [`);
22080
+ for (const server of servers) {
22081
+ lines.push(` ${quotePhpString(server.name)} => ${quotePhpString(server.url)},`);
22082
+ }
22083
+ lines.push(" ];");
22084
+ lines.push("");
22085
+ }
22086
+ const propKeyword = features.readonlyProps ? "private readonly" : "private";
22087
+ lines.push(` ${propKeyword} SerializerInterface $serializer;`);
22088
+ lines.push("");
22089
+ lines.push(" public function __construct(");
22090
+ lines.push(` ${propKeyword} HttpClientInterface $client,`);
22091
+ if (defaultServer) {
22092
+ lines.push(` ${propKeyword} string $baseUrl = self::DEFAULT_BASE_URL,`);
22093
+ } else {
22094
+ lines.push(` ${propKeyword} string $baseUrl,`);
22095
+ }
22096
+ lines.push(" ?SerializerInterface $serializer = null,");
22097
+ lines.push(" ) {");
22098
+ lines.push(" $this->serializer = $serializer ?? SerializerFactory::create();");
22099
+ lines.push(" }");
22100
+ const queries = ctx.document.operations.filter((op) => op.type === "query");
22101
+ const mutations = ctx.document.operations.filter((op) => op.type === "mutation");
22102
+ if (queries.length > 0) {
22103
+ lines.push("");
22104
+ lines.push(" // \u2500\u2500 Queries \u2500\u2500");
22105
+ for (const op of queries) {
22106
+ lines.push("");
22107
+ lines.push(generatePhpQuery(op, pctx));
22108
+ }
22109
+ }
22110
+ if (mutations.length > 0) {
22111
+ lines.push("");
22112
+ lines.push(" // \u2500\u2500 Mutations \u2500\u2500");
22113
+ for (const op of mutations) {
22114
+ lines.push("");
22115
+ lines.push(generatePhpMutation(op, pctx));
22116
+ }
22117
+ }
22118
+ lines.push("}");
22119
+ const code = lines.join("\n");
22120
+ const uses = ["Symfony\\Component\\Serializer\\SerializerInterface", "Symfony\\Contracts\\HttpClient\\HttpClientInterface"];
22121
+ if (code.includes("AbstractObjectNormalizer::")) {
22122
+ uses.push("Symfony\\Component\\Serializer\\Normalizer\\AbstractObjectNormalizer");
22123
+ }
22124
+ return phpFile(php.namespace, uses, code);
22125
+ }
22126
+
21165
22127
  // src/modes/mode.ts
21166
22128
  function getMode(name) {
21167
22129
  switch (name) {
@@ -21173,8 +22135,10 @@ function getMode(name) {
21173
22135
  return swiftMode;
21174
22136
  case "kotlin":
21175
22137
  return kotlinMode;
22138
+ case "php":
22139
+ return phpMode;
21176
22140
  default:
21177
- throw new Error(`Unknown mode: ${name}. Expected "full", "playwright", "swift", or "kotlin".`);
22141
+ throw new Error(`Unknown mode: ${name}. Expected "full", "playwright", "swift", "kotlin", or "php".`);
21178
22142
  }
21179
22143
  }
21180
22144
 
@@ -21289,25 +22253,40 @@ function filterUsedImports(names, source, importStatement) {
21289
22253
  // src/pipeline.ts
21290
22254
  async function generate(options) {
21291
22255
  const { input: input2, outputDir: outputDir2, name, mode: modeName } = options;
22256
+ const mode2 = getMode(modeName);
21292
22257
  const document = await parseOpenApiDocument((0, import_node_path.resolve)(input2));
21293
- if (modeName === "swift" || modeName === "kotlin") {
22258
+ if (mode2.nativeTarget) {
21294
22259
  hoistAnonymousSchemas(document);
21295
22260
  }
21296
22261
  document.schemas = topologicalSortSchemas(document.schemas);
21297
- const mode2 = getMode(modeName);
21298
22262
  const ctx = {
21299
22263
  document,
21300
22264
  zodSchemas: /* @__PURE__ */ new Map(),
21301
22265
  clientName: name,
21302
22266
  mode: modeName
21303
22267
  };
22268
+ if (modeName === "php") {
22269
+ ctx.php = {
22270
+ namespace: options.namespace ?? name,
22271
+ phpVersion: options.phpVersion ?? "8.1"
22272
+ };
22273
+ }
21304
22274
  const namedSchemas = new Set(document.schemas.keys());
21305
22275
  const convCtx = createConversionContext(namedSchemas, void 0, document.schemas);
21306
- if (modeName !== "swift" && modeName !== "kotlin") {
22276
+ if (!mode2.nativeTarget) {
21307
22277
  for (const [schemaName, schema] of document.schemas) {
21308
22278
  ctx.zodSchemas.set(schemaName, schemaToZod(schema, convCtx));
21309
22279
  }
21310
22280
  }
22281
+ if ("generateFiles" in mode2) {
22282
+ const rootDir = (0, import_node_path.resolve)(outputDir2, name);
22283
+ for (const [relPath, content] of mode2.generateFiles(ctx)) {
22284
+ const absPath = (0, import_node_path.join)(rootDir, relPath);
22285
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(absPath), { recursive: true });
22286
+ (0, import_node_fs.writeFileSync)(absPath, content, "utf-8");
22287
+ }
22288
+ return rootDir;
22289
+ }
21311
22290
  const parts = [];
21312
22291
  parts.push(mode2.generateImports(ctx));
21313
22292
  parts.push(mode2.generateHelperTypes(ctx));
@@ -21325,10 +22304,9 @@ async function generate(options) {
21325
22304
  }
21326
22305
  }
21327
22306
  parts.push(mode2.generateClient(document.operations, ctx));
21328
- const output = modeName === "swift" || modeName === "kotlin" ? parts.join("\n") : removeDeadCode(parts.join("\n"));
22307
+ const output = mode2.nativeTarget ? parts.join("\n") : removeDeadCode(parts.join("\n"));
21329
22308
  (0, import_node_fs.mkdirSync)((0, import_node_path.resolve)(outputDir2), { recursive: true });
21330
- const extMap = { swift: ".swift", kotlin: ".kt" };
21331
- const ext = extMap[modeName] ?? ".ts";
22309
+ const ext = mode2.fileExtension ?? ".ts";
21332
22310
  const outputPath = (0, import_node_path.resolve)(outputDir2, `${name}${ext}`);
21333
22311
  (0, import_node_fs.writeFileSync)(outputPath, output, "utf-8");
21334
22312
  return outputPath;
@@ -21545,21 +22523,28 @@ function generateSchemaSection(ctx, namedSchemas, convCtx) {
21545
22523
  return lines.join("\n");
21546
22524
  }
21547
22525
 
22526
+ // src/types.ts
22527
+ var PHP_VERSIONS = ["8.0", "8.1", "8.2", "8.3", "8.4", "8.5"];
22528
+
21548
22529
  // src/bin.ts
21549
22530
  var HELP = `
21550
- Usage: openapi-client <spec.json> <output-dir> -n <ClassName> [-m full|playwright|swift|kotlin]
22531
+ Usage: openapi-client <spec.json> <output-dir> -n <ClassName> [-m full|playwright|swift|kotlin|php]
21551
22532
 
21552
22533
  Options:
21553
- -n, --name <name> Client class name (required)
21554
- -m, --mode <mode> Generation mode: "full", "playwright", "swift", or "kotlin" (default: "full")
21555
- -v, --version Show version
21556
- -h, --help Show help
22534
+ -n, --name <name> Client class name (required)
22535
+ -m, --mode <mode> Generation mode: "full", "playwright", "swift", "kotlin", or "php" (default: "full")
22536
+ --namespace <ns> Root PHP namespace, php mode only (default: --name)
22537
+ --php-version <v> Target PHP version, php mode only: ${PHP_VERSIONS.join("|")} (default: "8.1")
22538
+ -v, --version Show version
22539
+ -h, --help Show help
21557
22540
  `;
21558
22541
  var { values, positionals } = (0, import_node_util.parseArgs)({
21559
22542
  args: process.argv.slice(2),
21560
22543
  options: {
21561
22544
  name: { type: "string", short: "n" },
21562
22545
  mode: { type: "string", short: "m", default: "full" },
22546
+ namespace: { type: "string" },
22547
+ "php-version": { type: "string", default: "8.1" },
21563
22548
  version: { type: "boolean", short: "v" },
21564
22549
  help: { type: "boolean", short: "h" }
21565
22550
  },
@@ -21590,12 +22575,36 @@ if (!TS_IDENTIFIER_RE.test(values.name)) {
21590
22575
  process.exit(1);
21591
22576
  }
21592
22577
  var mode = values.mode;
21593
- if (mode !== "full" && mode !== "playwright" && mode !== "swift" && mode !== "kotlin") {
21594
- console.error(`Error: --mode must be "full", "playwright", "swift", or "kotlin", got "${mode}".`);
22578
+ if (mode !== "full" && mode !== "playwright" && mode !== "swift" && mode !== "kotlin" && mode !== "php") {
22579
+ console.error(`Error: --mode must be "full", "playwright", "swift", "kotlin", or "php", got "${mode}".`);
21595
22580
  process.exit(1);
21596
22581
  }
22582
+ var phpVersion = values["php-version"];
22583
+ if (mode === "php") {
22584
+ const PHP_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
22585
+ const PHP_NAMESPACE_RE = /^[a-zA-Z_][a-zA-Z0-9_]*(\\[a-zA-Z_][a-zA-Z0-9_]*)*$/;
22586
+ if (!PHP_IDENTIFIER_RE.test(values.name)) {
22587
+ console.error(`Error: --name "${values.name}" is not a valid PHP class name.`);
22588
+ process.exit(1);
22589
+ }
22590
+ if (values.namespace !== void 0 && !PHP_NAMESPACE_RE.test(values.namespace)) {
22591
+ console.error(`Error: --namespace "${values.namespace}" is not a valid PHP namespace (expected e.g. "App\\Api\\Petstore").`);
22592
+ process.exit(1);
22593
+ }
22594
+ if (!PHP_VERSIONS.includes(phpVersion)) {
22595
+ console.error(`Error: --php-version must be one of ${PHP_VERSIONS.join(", ")}, got "${phpVersion}".`);
22596
+ process.exit(1);
22597
+ }
22598
+ } else {
22599
+ if (values.namespace !== void 0) {
22600
+ console.warn(`Warning: --namespace is only used in php mode, ignoring.`);
22601
+ }
22602
+ if (values["php-version"] !== "8.1") {
22603
+ console.warn(`Warning: --php-version is only used in php mode, ignoring.`);
22604
+ }
22605
+ }
21597
22606
  console.log(`Generating ${values.name} in ${mode} mode from ${input}...`);
21598
- generate({ input, outputDir, name: values.name, mode }).then((outputPath) => {
22607
+ generate({ input, outputDir, name: values.name, mode, namespace: values.namespace, phpVersion }).then((outputPath) => {
21599
22608
  console.log(`Generated: ${outputPath}`);
21600
22609
  }).catch((error) => {
21601
22610
  console.error("Generation failed:", error.message || error);