Skip to content

Commit 468540a

Browse files
authored
Merge pull request #129 from DoclerLabs/fix-content-type-construction-parameter-issue-128
Fix content type construction parameter issue 128
2 parents 3d37544 + efbdcef commit 468540a

19 files changed

+1821
-49
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
44
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
55
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
66

7+
## [11.1.3] - 2025.12.08
8+
### Fixed
9+
- Fixed content type construction parameter generation - issue #128, fixed
10+
- with multiple content type values
11+
- with content type value received from parameters
12+
713
## [11.1.2] - 2025.10.28
814
### Fixed
915
- Enum with invalid PHP constant value

example/PetStoreClient/doc/petstore3.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,20 @@
116116
"summary": "Add a new pet to the store",
117117
"description": "Add a new pet to the store",
118118
"operationId": "addPet",
119+
"parameters": [
120+
{
121+
"in" : "header",
122+
"name": "Content-Type",
123+
"required": true,
124+
"schema": {
125+
"type": "string",
126+
"enum": [
127+
"application/json",
128+
"application/xml"
129+
]
130+
}
131+
}
132+
],
119133
"requestBody": {
120134
"description": "Create a new pet in the store",
121135
"content": {

example/PetStoreClient/src/Request/AddPetRequest.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,22 @@
1111
namespace OpenApi\PetStoreClient\Request;
1212

1313
use OpenApi\PetStoreClient\Schema\Pet;
14+
use OpenApi\PetStoreClient\Schema\SerializableInterface;
1415

1516
class AddPetRequest implements RequestInterface
1617
{
17-
private Pet $pet;
18+
public const CONTENT_TYPE_APPLICATION_JSON = 'application/json';
19+
20+
public const CONTENT_TYPE_APPLICATION_XML = 'application/xml';
1821

1922
private string $contentType;
2023

21-
public function __construct(Pet $pet, string $contentType)
24+
private Pet $pet;
25+
26+
public function __construct(string $contentType, Pet $pet)
2227
{
23-
$this->pet = $pet;
2428
$this->contentType = $contentType;
29+
$this->pet = $pet;
2530
}
2631

2732
public function getContentType(): string
@@ -56,7 +61,11 @@ public function getCookies(): array
5661

5762
public function getHeaders(): array
5863
{
59-
return ['Content-Type' => $this->contentType];
64+
return array_merge(['Content-Type' => $this->contentType], array_map(static function ($value) {
65+
return $value instanceof SerializableInterface ? $value->toArray() : $value;
66+
}, array_filter(['Content-Type' => $this->contentType], static function ($value) {
67+
return null !== $value;
68+
})));
6069
}
6170

6271
/**

example/petstore3.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,20 @@
116116
"summary": "Add a new pet to the store",
117117
"description": "Add a new pet to the store",
118118
"operationId": "addPet",
119+
"parameters": [
120+
{
121+
"in" : "header",
122+
"name": "Content-Type",
123+
"required": true,
124+
"schema": {
125+
"type": "string",
126+
"enum": [
127+
"application/json",
128+
"application/xml"
129+
]
130+
}
131+
}
132+
],
119133
"requestBody": {
120134
"description": "Create a new pet in the store",
121135
"content": {

src/Generator/RequestGenerator.php

Lines changed: 48 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,13 @@ class RequestGenerator extends MutatorAccessorClassGeneratorAbstract
2828

2929
public const SUBDIRECTORY = 'Request/';
3030

31+
private const CONTENT_TYPE_PARAMETER_NAME = 'contentType';
32+
3133
/** @var SecurityStrategyInterface[] */
3234
private array $securityStrategies;
3335

36+
private bool $isContentTypeEnum = false;
37+
3438
public function __construct(
3539
string $baseNamespace,
3640
CodeBuilder $builder,
@@ -45,6 +49,7 @@ public function __construct(
4549
public function generate(Specification $specification, PhpFileCollection $fileRegistry): void
4650
{
4751
foreach ($specification->getOperations() as $operation) {
52+
$this->isContentTypeEnum = false;
4853
$this->generateRequest($fileRegistry, $operation, $specification);
4954
}
5055
}
@@ -92,6 +97,7 @@ protected function generateEnums(Request $request): array
9297
protected function generateProperties(Request $request, Operation $operation, Specification $specification): array
9398
{
9499
$statements = [];
100+
$fieldNames = [];
95101

96102
foreach ($request->fields as $field) {
97103
if ($field->isComposite()) {
@@ -117,13 +123,21 @@ protected function generateProperties(Request $request, Operation $operation, Sp
117123
}
118124

119125
$statements[] = $this->generateProperty($field);
126+
$fieldNames[] = $field->getPhpVariableName();
120127
}
121128

122-
$default = null;
123-
if (count($request->bodyContentTypes) < 2) {
129+
$default = null;
130+
$bodyContentTypesCount = count($request->bodyContentTypes);
131+
if ($bodyContentTypesCount < 2) {
124132
$default = $this->builder->val($request->bodyContentTypes[0] ?? '');
125133
}
126-
$statements[] = $this->builder->localProperty('contentType', 'string', 'string', false, $default);
134+
135+
if (
136+
($bodyContentTypesCount < 2 || !$this->phpVersion->isConstructorPropertyPromotionSupported())
137+
&& !in_array(self::CONTENT_TYPE_PARAMETER_NAME, $fieldNames, true)
138+
) {
139+
$statements[] = $this->builder->localProperty(self::CONTENT_TYPE_PARAMETER_NAME, 'string', 'string', false, $default);
140+
}
127141

128142
foreach ($this->securityStrategies as $securityStrategy) {
129143
array_push($statements, ...$securityStrategy->getProperties($operation, $specification));
@@ -137,6 +151,7 @@ protected function generateConstructor(
137151
Operation $operation,
138152
Specification $specification
139153
): ?ClassMethod {
154+
$paramNames = [];
140155
$params = [];
141156
$paramInits = [];
142157
$validations = [];
@@ -163,7 +178,16 @@ protected function generateConstructor(
163178
}
164179
}
165180

166-
$params[] = $param;
181+
$params[] = $param;
182+
$paramNames[] = $field->getPhpVariableName();
183+
184+
if (
185+
$field->isEnum()
186+
&& $this->phpVersion->isEnumSupported()
187+
&& $field->getPhpVariableName() === self::CONTENT_TYPE_PARAMETER_NAME
188+
) {
189+
$this->isContentTypeEnum = true;
190+
}
167191

168192
$paramInits[] = $this->builder->assign(
169193
$this->builder->localPropertyFetch($field->getPhpVariableName()),
@@ -177,14 +201,12 @@ protected function generateConstructor(
177201
array_push($paramInits, ...$securityStrategy->getConstructorParamInits($operation, $specification));
178202
}
179203

180-
if (count($request->bodyContentTypes) > 1) {
181-
$contentTypeVariableName = 'contentType';
182-
183-
$params[] = $this->builder->param($contentTypeVariableName)->setType('string');
204+
if (count($request->bodyContentTypes) > 1 && !in_array(self::CONTENT_TYPE_PARAMETER_NAME, $paramNames, true)) {
205+
$params[] = $this->builder->param(self::CONTENT_TYPE_PARAMETER_NAME)->setType('string');
184206

185207
$paramInits[] = $this->builder->assign(
186-
$this->builder->localPropertyFetch($contentTypeVariableName),
187-
$this->builder->var($contentTypeVariableName)
208+
$this->builder->localPropertyFetch(self::CONTENT_TYPE_PARAMETER_NAME),
209+
$this->builder->var(self::CONTENT_TYPE_PARAMETER_NAME)
188210
);
189211
}
190212

@@ -242,8 +264,14 @@ private function generateSetters(Request $request): array
242264

243265
private function generateGetContentType(): ClassMethod
244266
{
245-
$return = $this->builder->return($this->builder->localPropertyFetch('contentType'));
246-
$returnType = 'string';
267+
$localProperty = $this->builder->localPropertyFetch(self::CONTENT_TYPE_PARAMETER_NAME);
268+
$returnType = 'string';
269+
270+
if ($this->isContentTypeEnum) {
271+
$localProperty = $this->builder->propertyFetch($localProperty, 'value');
272+
}
273+
274+
$return = $this->builder->return($localProperty);
247275

248276
return $this
249277
->builder
@@ -452,8 +480,15 @@ private function generateGetHeadersMethod(
452480
$stmts = $this->getSecurityHeadersStmts($operation, $specification);
453481
$headers = $this->getSecurityHeaders($operation, $specification);
454482
if (!empty($request->bodyContentTypes)) {
455-
$headers['Content-Type'] = $this->builder->localPropertyFetch('contentType');
483+
$contentType = $this->builder->localPropertyFetch('contentType');
484+
485+
if ($this->isContentTypeEnum) {
486+
$contentType = $this->builder->propertyFetch($contentType, 'value');
487+
}
488+
489+
$headers['Content-Type'] = $contentType;
456490
}
491+
457492
$returnVal = $this->builder->array($headers);
458493
$fieldsArr = [];
459494
$returnType = 'array';
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file was generated by docler-labs/api-client-generator.
7+
*
8+
* Do not edit it manually.
9+
*/
10+
11+
namespace Test\Request;
12+
13+
use Test\Schema\ResourceTypeEnum;
14+
15+
class GetResourceByTypeRequest implements RequestInterface
16+
{
17+
private string $contentType = '';
18+
19+
public function __construct(private readonly ResourceTypeEnum $resourceType)
20+
{
21+
}
22+
23+
public function getContentType(): string
24+
{
25+
return $this->contentType;
26+
}
27+
28+
public function getMethod(): string
29+
{
30+
return 'GET';
31+
}
32+
33+
public function getRoute(): string
34+
{
35+
return strtr('v1/{resource-type}/resource', ['{resource-type}' => $this->resourceType->value]);
36+
}
37+
38+
public function getQueryParameters(): array
39+
{
40+
return [];
41+
}
42+
43+
public function getRawQueryParameters(): array
44+
{
45+
return [];
46+
}
47+
48+
public function getCookies(): array
49+
{
50+
return [];
51+
}
52+
53+
public function getHeaders(): array
54+
{
55+
return [];
56+
}
57+
58+
public function getBody()
59+
{
60+
return null;
61+
}
62+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file was generated by docler-labs/api-client-generator.
7+
*
8+
* Do not edit it manually.
9+
*/
10+
11+
namespace Test\Request;
12+
13+
use Test\Schema\ResourceFilter;
14+
use Test\Schema\SerializableInterface;
15+
16+
class GetResourcesRequest implements RequestInterface
17+
{
18+
private ?int $filterById = null;
19+
20+
private ?string $filterByName = null;
21+
22+
private ?array $filterByIds = null;
23+
24+
private ?ResourceFilter $filter = null;
25+
26+
private string $contentType = '';
27+
28+
public function __construct(private readonly AuthenticationCredentials $credentials)
29+
{
30+
}
31+
32+
public function getContentType(): string
33+
{
34+
return $this->contentType;
35+
}
36+
37+
public function setFilterById(int $filterById): self
38+
{
39+
$this->filterById = $filterById;
40+
41+
return $this;
42+
}
43+
44+
public function setFilterByName(string $filterByName): self
45+
{
46+
$this->filterByName = $filterByName;
47+
48+
return $this;
49+
}
50+
51+
/**
52+
* @param int[] $filterByIds
53+
*/
54+
public function setFilterByIds(array $filterByIds): self
55+
{
56+
$this->filterByIds = $filterByIds;
57+
58+
return $this;
59+
}
60+
61+
public function setFilter(ResourceFilter $filter): self
62+
{
63+
$this->filter = $filter;
64+
65+
return $this;
66+
}
67+
68+
public function getMethod(): string
69+
{
70+
return 'GET';
71+
}
72+
73+
public function getRoute(): string
74+
{
75+
return 'v1/resources';
76+
}
77+
78+
public function getQueryParameters(): array
79+
{
80+
return array_map(static function ($value) {
81+
return $value instanceof SerializableInterface ? $value->toArray() : $value;
82+
}, array_filter(['filterById' => $this->filterById, 'filterByName' => $this->filterByName, 'filterByIds' => $this->filterByIds, 'filter' => $this->filter], static function ($value) {
83+
return null !== $value;
84+
}));
85+
}
86+
87+
public function getRawQueryParameters(): array
88+
{
89+
return ['filterById' => $this->filterById, 'filterByName' => $this->filterByName, 'filterByIds' => $this->filterByIds, 'filter' => $this->filter];
90+
}
91+
92+
public function getCookies(): array
93+
{
94+
return [];
95+
}
96+
97+
public function getHeaders(): array
98+
{
99+
return ['Authorization' => sprintf('Basic %s', base64_encode(sprintf('%s:%s', $this->credentials->getUsername(), $this->credentials->getPassword())))];
100+
}
101+
102+
public function getBody()
103+
{
104+
return null;
105+
}
106+
}

0 commit comments

Comments
 (0)