Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

## [11.1.3] - 2025.12.08
### Fixed
- Fixed content type construction parameter generation - issue #128, fixed
- with multiple content type values
- with content type value received from parameters

## [11.1.2] - 2025.10.28
### Fixed
- Enum with invalid PHP constant value
Expand Down
14 changes: 14 additions & 0 deletions example/PetStoreClient/doc/petstore3.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,20 @@
"summary": "Add a new pet to the store",
"description": "Add a new pet to the store",
"operationId": "addPet",
"parameters": [
{
"in" : "header",
"name": "Content-Type",
"required": true,
"schema": {
"type": "string",
"enum": [
"application/json",
"application/xml"
]
}
}
],
"requestBody": {
"description": "Create a new pet in the store",
"content": {
Expand Down
17 changes: 13 additions & 4 deletions example/PetStoreClient/src/Request/AddPetRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,22 @@
namespace OpenApi\PetStoreClient\Request;

use OpenApi\PetStoreClient\Schema\Pet;
use OpenApi\PetStoreClient\Schema\SerializableInterface;

class AddPetRequest implements RequestInterface
{
private Pet $pet;
public const CONTENT_TYPE_APPLICATION_JSON = 'application/json';
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these constants used anywhere?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not from the Client. I extended the example with ContentType header enum and test run re-generated this example.
As I see these constants consumers. From PHP 8.1 it's not generated, becasue the enum class is used.


public const CONTENT_TYPE_APPLICATION_XML = 'application/xml';

private string $contentType;

public function __construct(Pet $pet, string $contentType)
private Pet $pet;

public function __construct(string $contentType, Pet $pet)
{
$this->pet = $pet;
$this->contentType = $contentType;
$this->pet = $pet;
}

public function getContentType(): string
Expand Down Expand Up @@ -56,7 +61,11 @@ public function getCookies(): array

public function getHeaders(): array
{
return ['Content-Type' => $this->contentType];
return array_merge(['Content-Type' => $this->contentType], array_map(static function ($value) {
return $value instanceof SerializableInterface ? $value->toArray() : $value;
}, array_filter(['Content-Type' => $this->contentType], static function ($value) {
return null !== $value;
})));
}

/**
Expand Down
14 changes: 14 additions & 0 deletions example/petstore3.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,20 @@
"summary": "Add a new pet to the store",
"description": "Add a new pet to the store",
"operationId": "addPet",
"parameters": [
{
"in" : "header",
"name": "Content-Type",
"required": true,
"schema": {
"type": "string",
"enum": [
"application/json",
"application/xml"
]
}
}
],
"requestBody": {
"description": "Create a new pet in the store",
"content": {
Expand Down
61 changes: 48 additions & 13 deletions src/Generator/RequestGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ class RequestGenerator extends MutatorAccessorClassGeneratorAbstract

public const SUBDIRECTORY = 'Request/';

private const CONTENT_TYPE_PARAMETER_NAME = 'contentType';

/** @var SecurityStrategyInterface[] */
private array $securityStrategies;

private bool $isContentTypeEnum = false;

public function __construct(
string $baseNamespace,
CodeBuilder $builder,
Expand All @@ -45,6 +49,7 @@ public function __construct(
public function generate(Specification $specification, PhpFileCollection $fileRegistry): void
{
foreach ($specification->getOperations() as $operation) {
$this->isContentTypeEnum = false;
$this->generateRequest($fileRegistry, $operation, $specification);
}
}
Expand Down Expand Up @@ -92,6 +97,7 @@ protected function generateEnums(Request $request): array
protected function generateProperties(Request $request, Operation $operation, Specification $specification): array
{
$statements = [];
$fieldNames = [];

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

$statements[] = $this->generateProperty($field);
$fieldNames[] = $field->getPhpVariableName();
}

$default = null;
if (count($request->bodyContentTypes) < 2) {
$default = null;
$bodyContentTypesCount = count($request->bodyContentTypes);
if ($bodyContentTypesCount < 2) {
$default = $this->builder->val($request->bodyContentTypes[0] ?? '');
}
$statements[] = $this->builder->localProperty('contentType', 'string', 'string', false, $default);

if (
($bodyContentTypesCount < 2 || !$this->phpVersion->isConstructorPropertyPromotionSupported())
&& !in_array(self::CONTENT_TYPE_PARAMETER_NAME, $fieldNames, true)
) {
$statements[] = $this->builder->localProperty(self::CONTENT_TYPE_PARAMETER_NAME, 'string', 'string', false, $default);
}

foreach ($this->securityStrategies as $securityStrategy) {
array_push($statements, ...$securityStrategy->getProperties($operation, $specification));
Expand All @@ -137,6 +151,7 @@ protected function generateConstructor(
Operation $operation,
Specification $specification
): ?ClassMethod {
$paramNames = [];
$params = [];
$paramInits = [];
$validations = [];
Expand All @@ -163,7 +178,16 @@ protected function generateConstructor(
}
}

$params[] = $param;
$params[] = $param;
$paramNames[] = $field->getPhpVariableName();

if (
$field->isEnum()
&& $this->phpVersion->isEnumSupported()
&& $field->getPhpVariableName() === self::CONTENT_TYPE_PARAMETER_NAME
) {
$this->isContentTypeEnum = true;
}

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

if (count($request->bodyContentTypes) > 1) {
$contentTypeVariableName = 'contentType';

$params[] = $this->builder->param($contentTypeVariableName)->setType('string');
if (count($request->bodyContentTypes) > 1 && !in_array(self::CONTENT_TYPE_PARAMETER_NAME, $paramNames, true)) {
$params[] = $this->builder->param(self::CONTENT_TYPE_PARAMETER_NAME)->setType('string');

$paramInits[] = $this->builder->assign(
$this->builder->localPropertyFetch($contentTypeVariableName),
$this->builder->var($contentTypeVariableName)
$this->builder->localPropertyFetch(self::CONTENT_TYPE_PARAMETER_NAME),
$this->builder->var(self::CONTENT_TYPE_PARAMETER_NAME)
);
}

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

private function generateGetContentType(): ClassMethod
{
$return = $this->builder->return($this->builder->localPropertyFetch('contentType'));
$returnType = 'string';
$localProperty = $this->builder->localPropertyFetch(self::CONTENT_TYPE_PARAMETER_NAME);
$returnType = 'string';

if ($this->isContentTypeEnum) {
$localProperty = $this->builder->propertyFetch($localProperty, 'value');
}

$return = $this->builder->return($localProperty);

return $this
->builder
Expand Down Expand Up @@ -452,8 +480,15 @@ private function generateGetHeadersMethod(
$stmts = $this->getSecurityHeadersStmts($operation, $specification);
$headers = $this->getSecurityHeaders($operation, $specification);
if (!empty($request->bodyContentTypes)) {
$headers['Content-Type'] = $this->builder->localPropertyFetch('contentType');
$contentType = $this->builder->localPropertyFetch('contentType');

if ($this->isContentTypeEnum) {
$contentType = $this->builder->propertyFetch($contentType, 'value');
}

$headers['Content-Type'] = $contentType;
}

$returnVal = $this->builder->array($headers);
$fieldsArr = [];
$returnType = 'array';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

declare(strict_types=1);

/*
* This file was generated by docler-labs/api-client-generator.
*
* Do not edit it manually.
*/

namespace Test\Request;

use Test\Schema\ResourceTypeEnum;

class GetResourceByTypeRequest implements RequestInterface
{
private string $contentType = '';

public function __construct(private readonly ResourceTypeEnum $resourceType)
{
}

public function getContentType(): string
{
return $this->contentType;
}

public function getMethod(): string
{
return 'GET';
}

public function getRoute(): string
{
return strtr('v1/{resource-type}/resource', ['{resource-type}' => $this->resourceType->value]);
}

public function getQueryParameters(): array
{
return [];
}

public function getRawQueryParameters(): array
{
return [];
}

public function getCookies(): array
{
return [];
}

public function getHeaders(): array
{
return [];
}

public function getBody()
{
return null;
}
}
106 changes: 106 additions & 0 deletions test/suite/functional/Generator/Request/GetResourcesRequest83.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<?php

declare(strict_types=1);

/*
* This file was generated by docler-labs/api-client-generator.
*
* Do not edit it manually.
*/

namespace Test\Request;

use Test\Schema\ResourceFilter;
use Test\Schema\SerializableInterface;

class GetResourcesRequest implements RequestInterface
{
private ?int $filterById = null;

private ?string $filterByName = null;

private ?array $filterByIds = null;

private ?ResourceFilter $filter = null;

private string $contentType = '';

public function __construct(private readonly AuthenticationCredentials $credentials)
{
}

public function getContentType(): string
{
return $this->contentType;
}

public function setFilterById(int $filterById): self
{
$this->filterById = $filterById;

return $this;
}

public function setFilterByName(string $filterByName): self
{
$this->filterByName = $filterByName;

return $this;
}

/**
* @param int[] $filterByIds
*/
public function setFilterByIds(array $filterByIds): self
{
$this->filterByIds = $filterByIds;

return $this;
}

public function setFilter(ResourceFilter $filter): self
{
$this->filter = $filter;

return $this;
}

public function getMethod(): string
{
return 'GET';
}

public function getRoute(): string
{
return 'v1/resources';
}

public function getQueryParameters(): array
{
return array_map(static function ($value) {
return $value instanceof SerializableInterface ? $value->toArray() : $value;
}, array_filter(['filterById' => $this->filterById, 'filterByName' => $this->filterByName, 'filterByIds' => $this->filterByIds, 'filter' => $this->filter], static function ($value) {
return null !== $value;
}));
}

public function getRawQueryParameters(): array
{
return ['filterById' => $this->filterById, 'filterByName' => $this->filterByName, 'filterByIds' => $this->filterByIds, 'filter' => $this->filter];
}

public function getCookies(): array
{
return [];
}

public function getHeaders(): array
{
return ['Authorization' => sprintf('Basic %s', base64_encode(sprintf('%s:%s', $this->credentials->getUsername(), $this->credentials->getPassword())))];
}

public function getBody()
{
return null;
}
}
Loading