openapi-generator
openapi-generator copied to clipboard
[BUG] [PHP] Response models not using namespaces
Bug Report Checklist
- [x] Have you provided a full/minimal spec to reproduce the issue?
- [x] Have you validated the input using an OpenAPI validator (example)?
- [x] What's the version of OpenAPI Generator used?
- [x] Have you search for related issues/PRs?
- [x] What's the actual output vs expected output?
- [ ] [Optional] Bounty to sponsor the fix (example)
Description
Some of the PHP code generated by the openapi-generator seems to be references response models incorrectly, resulting in classes not being found.
openapi-generator version
I am using version 4.0.1 on the openapi-generator on Mac OS 10.14.5.
OpenAPI declaration file content or url
200UserAuthenticatedResponse:
description: Successful user login response
headers:
X-Request-Id:
$ref: "#/components/headers/X-Request-Id"
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/ApiResponseCommonValues"
- type: object
required:
- details
properties:
details:
$ref: "#/components/schemas/LoginAuthenticatedResponse"
switch($statusCode) {
case 200:
if ('ApiResponseCommonValues' === '\SplFileObject') {
$content = $responseBody; //stream goes to serializer
} else {
$content = $responseBody->getContents();
}
return [
ObjectSerializer::deserialize($content, 'ApiResponseCommonValues', []),
$response->getStatusCode(),
$response->getHeaders()
];
case 400:
if ('\TheSupportGroup\easyfundraising\ApiClient\Model\ApiBadResponseError' === '\SplFileObject') {
$content = $responseBody; //stream goes to serializer
} else {
$content = $responseBody->getContents();
}
return [
ObjectSerializer::deserialize($content, '\TheSupportGroup\easyfundraising\ApiClient\Model\ApiBadResponseError', []),
$response->getStatusCode(),
$response->getHeaders()
];
case 401:
if ('\TheSupportGroup\easyfundraising\ApiClient\Model\ApiBadResponseError' === '\SplFileObject') {
$content = $responseBody; //stream goes to serializer
} else {
$content = $responseBody->getContents();
}
return [
ObjectSerializer::deserialize($content, '\TheSupportGroup\easyfundraising\ApiClient\Model\ApiBadResponseError', []),
$response->getStatusCode(),
$response->getHeaders()
];
case 404:
if ('\TheSupportGroup\easyfundraising\ApiClient\Model\ApiSimpleResponseError' === '\SplFileObject') {
$content = $responseBody; //stream goes to serializer
} else {
$content = $responseBody->getContents();
}
return [
ObjectSerializer::deserialize($content, '\TheSupportGroup\easyfundraising\ApiClient\Model\ApiSimpleResponseError', []),
$response->getStatusCode(),
$response->getHeaders()
];
case 500:
if ('\TheSupportGroup\easyfundraising\ApiClient\Model\ApiResponseError' === '\SplFileObject') {
$content = $responseBody; //stream goes to serializer
} else {
$content = $responseBody->getContents();
}
return [
ObjectSerializer::deserialize($content, '\TheSupportGroup\easyfundraising\ApiClient\Model\ApiResponseError', []),
$response->getStatusCode(),
$response->getHeaders()
];
}
Many of the response models are ok, but the reference to ApiResponseCommonValues
is invalid. The class should be references as '\TheSupportGroup\easyfundraising\ApiClient\Model\ApiResponseCommonValues' the same way as all other response models.
Command line used for generation
docker run --rm -v ${PWD}:/local openapitools/openapi-generator-cli generate
-i /local/openapi.yaml
-g php
-o /local/out/php
--skip-validate-spec
--package-name "easyfundraising APIv3 PHP Client"
--invoker-package TheSupportGroup\easyfundraising\ApiClient
--additional-properties variableNamingConvention=camelCase
We get the same thing too
UnsplashImageMixin:
allOf:
- type: object
properties:
user:
type: object
properties:
link:
type: string
format: uri
maxLength: 1024
pattern: "^(?:[a-z0-9\\.\\-\\+]*)://(?:\\S+(?::\\S*)?@)?(?:(?:25[0-5]|2[0-4]\\\
d|[0-1]?\\d?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|[0-1]?\\d?\\\
d)){3}|\\[[0-9a-f:\\.]+\\]|([a-z\xA1-\uFFFF0-9](?:[a-z\xA1\
-\uFFFF0-9-]{0,61}[a-z\xA1-\uFFFF0-9])?(?:\\.(?!-)[a-z\xA1\
-\uFFFF0-9-]{1,63}(?<!-))*\\.(?!-)(?:[a-z\xA1-\uFFFF-]{2,63}|xn--[a-z0-9]{1,59})(?<!-)\\\
.?|localhost))(?::\\d{2,5})?(?:[/?#][^\\s]*)?\\Z"
first_name:
type: string
description: First Name
last_name:
type: string
description: Last Name
image:
type: string
format: uri
maxLength: 1024
pattern: "^(?:[a-z0-9\\.\\-\\+]*)://(?:\\S+(?::\\S*)?@)?(?:(?:25[0-5]|2[0-4]\\\
d|[0-1]?\\d?\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|[0-1]?\\d?\\\
d)){3}|\\[[0-9a-f:\\.]+\\]|([a-z\xA1-\uFFFF0-9](?:[a-z\xA1\
-\uFFFF0-9-]{0,61}[a-z\xA1-\uFFFF0-9])?(?:\\.(?!-)[a-z\xA1\
-\uFFFF0-9-]{1,63}(?<!-))*\\.(?!-)(?:[a-z\xA1-\uFFFF-]{2,63}|xn--[a-z0-9]{1,59})(?<!-)\\\
.?|localhost))(?::\\d{2,5})?(?:[/?#][^\\s]*)?\\Z"
NearestCity:
allOf:
- $ref: '#/components/schemas/CreatedUpdatedMixin'
- type: object
properties:
id:
type: integer
format: int64
code:
type: string
description: City Code
name:
type: string
description: City Name
url_name:
type: string
description: slugified name
unsplash_image:
readOnly: true
allOf:
- $ref: "#/components/schemas/UnsplashImageMixin"
City:
allOf:
- $ref: '#/components/schemas/NearestCity'
- type: object
properties:
population:
type: integer
format: int64
student_population:
type: integer
format: int64
{#3486
+"id": 1
+"name": "Aberdeen"
+"url_name": "aberdeen"
+"population": 215000
+"student_population": 14990
+"latitude": "57.1497170"
+"longitude": "-2.0942780"
+"code": "ABD"
+"image_id": "XziNJnO9iSM"
+"unsplash_image": {#3485
+"user": {#3488
+"link": "https://unsplash.com/@redjulz11"
+"last_name": "Adams"
+"first_name": "Julie"
}
+"image": "https://images.unsplash.com/photo-1579723251184-cb96c6bcac33?ixid=MXwxODE4ODN8MHwxfGFsbHx8fHx8fHx8&ixlib=rb-1.2.1"
}
+"header_image_desktop": "https://images.unsplash.com/photo-1579723251184-cb96c6bcac33?ixid=MXwxODE4ODN8MHwxfGFsbHx8fHx8fHx8&ixlib=rb-1.2.1&fit=crop&crop=middle&w=1390&h=620"
+"header_image_mobile": "https://images.unsplash.com/photo-1579723251184-cb96c6bcac33?ixid=MXwxODE4ODN8MHwxfGFsbHx8fHx8fHx8&ixlib=rb-1.2.1&fit=crop&fp-x=.260&w=360&h=740"
+"card_image": "https://images.unsplash.com/photo-1579723251184-cb96c6bcac33?ixid=MXwxODE4ODN8MHwxfGFsbHx8fHx8fHx8&ixlib=rb-1.2.1&fit=crop&crop=middle"
+"halfmast_image": "https://images.unsplash.com/photo-1579723251184-cb96c6bcac33?ixid=MXwxODE4ODN8MHwxfGFsbHx8fHx8fHx8&ixlib=rb-1.2.1&fit=crop&crop=middle"
+"header_image_lg_params": "fit=crop&crop=middle"
+"header_image_sm_params": "fit=crop&fp-x=.260"
+"card_image_params": "fit=crop&crop=middle"
+"halfmast_image_params": "fit=crop&crop=middle"
}
{#3485
+"user": {#3488
+"link": "https://unsplash.com/@redjulz11"
+"last_name": "Adams"
+"first_name": "Julie"
}
+"image": "https://images.unsplash.com/photo-1579723251184-cb96c6bcac33?ixid=MXwxODE4ODN8MHwxfGFsbHx8fHx8fHx8&ixlib=rb-1.2.1"
}
local.ERROR: Class 'UnsplashImageMixin' not found {"exception":"[object] (Symfony\\Component\\Debug\\Exception\\FatalThrowableError(code: 0): Class 'UnsplashImageMixin' not found at /var/www/vendor/getasnap/geo-api-php-sdk/lib/ObjectSerializer.php:414)"} {"stacktrace":"#0 /var/www/vendor/getasnap/geo-api-php-sdk/lib/ObjectSerializer.php(439): SnapGeo\\ObjectSerializer::deserialize(Object(stdClass), 'UnsplashImageMi...', NULL)\n#1 /var/www/vendor/getasnap/geo-api-php-sdk/lib/ObjectSerializer.php(331): SnapGeo\\ObjectSerializer::deserialize(Object(stdClass), '\\\\SnapGeo\\\\Model\\\\...', NULL)\n#2 /var/www/vendor/getasnap/geo-api-php-sdk/lib/Api/GeoApi.php(187): SnapGeo\\ObjectSerializer::deserialize(Array, '\\\\SnapGeo\\\\Model\\\\...', Array)\n#3 /var/www/vendor/getasnap/geo-api-php-sdk/lib/Api/GeoApi.php(128): SnapGeo\\Api\\GeoApi->citiesWithHttpInfo()\n#4 /var/www/app/Services/CitiesService.php(42): SnapGeo\\Api\\GeoApi->cities()\n#5 /var/www/app/Services/CitiesService.php(36):
java -jar bin/openapi-generator-cli.jar version 5.0.0-SNAPSHOT
Also just tried it with the latest release & same error 5.0.0
local.ERROR: Class 'UnsplashImageMixin' not found {"exception":"[object] (Symfony\\Component\\Debug\\Exception\\FatalThrowableError(code: 0): Class 'UnsplashImageMixin' not found at /var/www/vendor/getasnap/geo-api-php-sdk/lib/ObjectSerializer.php:447)"} {"stacktrace":"#0 /var/www/vendor/getasnap/geo-api-php-sdk/lib/ObjectSerializer.php(475): SnapGeo\\ObjectSerializer::deserialize(Object(stdClass), 'UnsplashImageMi...', NULL)\n#1 /var/www/vendor/getasnap/geo-api-php-sdk/lib/ObjectSerializer.php(328): SnapGeo\\ObjectSerializer::deserialize(Object(stdClass), '\\\\SnapGeo\\\\Model\\\\...', NULL)\n#2 /var/www/vendor/getasnap/geo-api-php-sdk/lib/Api/GeoApi.php(187): SnapGeo\\ObjectSerializer::deserialize(Array, '\\\\SnapGeo\\\\Model\\\\...', Array)\n#3 /var/www/vendor/getasnap/geo-api-php-sdk/lib/Api/GeoApi.php(128): SnapGeo\\Api\\GeoApi->citiesWithHttpInfo()\n#4 /var/www/app/Services/CitiesService.php(42): SnapGeo\\Api\\GeoApi->cities()\n#5 /var/www/app/Services/CitiesService.php(62): App\\Services\\CitiesService->populateCache()
The protected static $openAPITypes
array of every model does not include the full namespace and it should include it for this to work.
This also happens if you are using allOf to for example add descriptions or the nullable field to references. Otherwise it generates nicely. Seems like for some reason allOf causes the namespace to be stripped from the dataType
property.
Seems to be something here: https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java#L2004-L2027
With allOf and friends it uses the ComposedSchema path I think. So it seems that some how it fails to grab the namespace there.
Or maybe this doesn't trip correctly: https://github.com/OpenAPITools/openapi-generator/blob/master/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractPhpCodegen.java#L459-L467
I can confirm this issue still exists in version 6.1.0
I wrote a PowerShell script to fix the models:
$basePath = ".\OpenAPIClient-php\lib\Model\"
$files = Get-ChildItem *.php -Path $basePath -Name
$models = $files | ForEach-Object { $_ -replace ".php", "" }
Write-Output "Fixing models..."
Foreach($file in $files) {
$path = $basePath + $file
$content = Get-Content -path $path -Raw
Foreach($model in $models) {
$content = $content -replace "=> '$model'", "=> '\OpenAPI\Client\Model\$model'"
$content = $content -replace "=> '$model\[\]'", "=> '\OpenAPI\Client\Model\$model[]'"
}
Set-Content -Path $path -Value $content
}
Write-Output "Fixing models done."
I updated the codegen to 7.0.1 and this issue didn't occur to me (I was on 5.x.x before and got it).
I think this issue can be closed now ?
FYI. There's also a php-nextgen
client generator that you may want to try and let us know if you've any feedback.
For the -g
option, instead of php
? I will try that and see.
What's the difference ?
yes, replace php
with php-nextgen
. you may want to try the latest snapshot version (links in the readme) with more enhancements, bug fixes
one difference is strict type support.
Couldn't try it "functionnally" yet but It looks like a good start.
I have a few things to say, but it's only my opinion :
In Models :
For non nullable properties, you could take advantage of typed argument to avoid generating code that check is passed agument is null and then throw your own Exception. Php default Error for passing wrong type is self-explanatory
On latest jar (openapi-generator-cli-7.1.0-20231003.110058-48.jar
) it generate
public function setPlatform(?string $platform): static
{
if (is_null($platform)) {
throw new InvalidArgumentException('non-nullable platform cannot be null');
}
$this->container['platform'] = $platform;
return $this;
}
While it could generate
public function setPlatform(string $platform): static
{
$this->container['platform'] = $platform;
return $this;
}
Same for getters that can return null values while the property is not nullable
And it's maybe a technical issue I don't know of, but I gues you could generate properties and "real getter/setter" instead of a container
array ?
Could be a bore with uninitialized
property state though, but it should happen only if property is not nullable and the developper didn't set it.
in Apis:
Class properties are not typed
Method arugments and return are not typed (they are in the constructor though)
Shouldn't methods like ***WithHttpInfo()
and ***Request()
be private/protected ? it's kinda unlikely these methods will be called from outside.
That's just a quick opinion after I generated a small api.
FYI. There's also a
php-nextgen
client generator that you may want to try and let us know if you've any feedback.
I can confirm this issue still exists in version 7.2.0
alekciy@alekciy-Lenovo:/tmp$ openapi-generator-cli --version
openapi-generator-cli 7.2.0
commit : fe638d0
built : -999999999-01-01T00:00:00+18:00
source : https://github.com/openapitools/openapi-generator
docs : https://openapi-generator.tech/
OS:
alekciy@alekciy-Lenovo:/tmp$ lsb_release -a
LSB Version: core-11.1.0ubuntu2-noarch:printing-11.1.0ubuntu2-noarch:security-11.1.0ubuntu2-noarch
Distributor ID: Ubuntu
Description: Ubuntu 20.04.6 LTS
Release: 20.04
Codename: focal
Gen lib:
alekciy@alekciy-Lenovo:/tmp$ git clone [email protected]:yandex-market/yandex-market-partner-api.git
Cloning into «yandex-market-partner-api»...
remote: Enumerating objects: 2044, done.
remote: Counting objects: 100% (812/812), done.
remote: Compressing objects: 100% (382/382), done.
remote: Total 2044 (delta 484), reused 687 (delta 430), pack-reused 1232
Unpacking objects: 100% (2044/2044), 517.16 КиБ | 1.27 МиБ/с, done.
alekciy@alekciy-Lenovo:/tmp$ cd yandex-market-partner-api/
alekciy@alekciy-Lenovo:/tmp/yandex-market-partner-api$ mkdir lib && openapi-generator-cli generate --invoker-package YandexMarket -i openapi/openapi.yaml -g php-nextgen -o ./lib
[main] INFO o.o.codegen.DefaultGenerator - Generating with dryRun=false
[main] INFO o.o.c.ignore.CodegenIgnoreProcessor - No .openapi-generator-ignore file found.
[main] INFO o.o.codegen.DefaultGenerator - OpenAPI Generator: php-nextgen (client)
[main] INFO o.o.codegen.DefaultGenerator - Generator 'php-nextgen' is considered beta.
[main] INFO o.o.c.languages.AbstractPhpCodegen - Environment variable PHP_POST_PROCESS_FILE not defined so the PHP code may not be properly formatted. To define it, try 'export PHP_POST_PROCESS_FILE="/usr/local/bin/prettier --write"' (Linux/Mac)
[main] INFO o.o.c.languages.AbstractPhpCodegen - NOTE: To enable file post-processing, 'enablePostProcessFile' must be set to `true` (--enable-post-process-file for CLI).
[main] INFO o.o.codegen.DefaultGenerator - Model SendFileToChatRequest not generated since it's marked as unused (due to form parameters) and `skipFormModel` (global property) set to true (default)
[main] WARN o.o.codegen.DefaultCodegen - Unknown `format` decimal detected for type `number`. Defaulting to `number`
[main] WARN o.o.codegen.DefaultCodegen - Unknown `format` decimal detected for type `number`. Defaulting to `number`
[main] WARN o.o.codegen.DefaultCodegen - Unknown `format` decimal detected for type `number`. Defaulting to `number`
[main] WARN o.o.codegen.DefaultCodegen - Unknown `format` decimal detected for type `number`. Defaulting to `number`
...
[main] INFO o.o.codegen.TemplateManager - writing file /tmp/yandex-market-partner-api/./lib/.openapi-generator/VERSION
[main] INFO o.o.codegen.TemplateManager - writing file /tmp/yandex-market-partner-api/./lib/.openapi-generator/FILES
################################################################################
# Thanks for using OpenAPI Generator. #
# Please consider donation to help us maintain this project 🙏 #
# https://opencollective.com/openapi_generator/donate #
################################################################################
alekciy@alekciy-Lenovo:/tmp/yandex-market-partner-api$ tail -n +245 lib/src/Api/OutletsApi.php | head
if ('\YandexMarket\Model\ApiClientDataErrorResponse' === '\SplFileObject') {
$content = $response->getBody(); //stream goes to serializer
} else {
$content = (string) $response->getBody();
if ('\YandexMarket\Model\ApiClientDataErrorResponse' !== 'string') {
try {
$content = json_decode($content, false, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $exception) {
throw new ApiException(
sprintf(
Not use YandexMarket\Model\ApiClientDataErrorResponse::class, use string '\YandexMarket\Model\ApiClientDataErrorResponse'.
Please tell me. What could be the problem?