openapi-generator icon indicating copy to clipboard operation
openapi-generator copied to clipboard

[BUG] [PHP] Response models not using namespaces

Open darren-potter opened this issue 5 years ago • 3 comments

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

darren-potter avatar Jun 10 '19 14:06 darren-potter

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()

martyzz1 avatar Jan 29 '21 12:01 martyzz1

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

EraYaN avatar Nov 09 '21 15:11 EraYaN

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."

darko1979 avatar Sep 16 '22 06:09 darko1979

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 ?

etshy avatar Oct 03 '23 08:10 etshy

FYI. There's also a php-nextgen client generator that you may want to try and let us know if you've any feedback.

wing328 avatar Oct 03 '23 10:10 wing328

For the -g option, instead of php ? I will try that and see. What's the difference ?

etshy avatar Oct 03 '23 10:10 etshy

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.

wing328 avatar Oct 03 '23 10:10 wing328

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.

etshy avatar Oct 03 '23 12:10 etshy

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?

alekciy avatar Jan 26 '24 06:01 alekciy