api-platform / core

The server component of API Platform: hypermedia and GraphQL APIs in minutes
https://api-platform.com
MIT License
2.42k stars 863 forks source link

Non-mapped properties and non-mapped nested classes are not shown in swagger output since update to v2.4 #2868

Closed remoteclient closed 4 years ago

remoteclient commented 5 years ago

As the title suggests, non-mapped properties are not shown in swagger output anymore. There are different scenarios. Sometimes one entity has a non-mapped property, some entities have other non-mapped entities with naturally also non-mapped properties. They all have in commen, that they don't appear in swagger anymore. They also have propper getters and setters. Serialization/Dezerialization of them are working like before.

remoteclient commented 5 years ago

With this post I want to bring some clarity to the issue and show some investigation results. 'Therefore I will write two post with similar issues.

First: I have a Pet Entity which holds an array called $sexTypeChoices and its getter.

    /**
     * @var array
     */
    public static $sexTypeChoices = [
        self::SEX_TYPE_FEMALE => 'sex_type.female',
        self::SEX_TYPE_MALE => 'sex_type.male'
    ];

    /**
     * @return array
     */
    public static function getSexTypeChoices(): array
    {
        return self::$sexTypeChoices;
    }

This one has an extra endpoint where only this property delivered to the client (with extra serialization group).

<collectionOperation name="sex_type_choices">
    <attribute name="method">GET</attribute>
    <attribute name="access_control">is_granted('ROLE_MWS_NC_LIST')</attribute>
    <attribute name="path">pets/sex_type_choices</attribute>
    <attribute name="controller">MWS\NutritionCalculatorBundle\Controller\SexTypeChoices</attribute>
    <attribute name="normalization_context">
        <attribute name="groups">
            <attribute name="group">mws_nc_sex_type_choices_read</attribute>
        </attribute>
    </attribute>
</collectionOperation>

If I call this endpint I get the correct result but the the swagger part which shows the expected result is empty:

[
  {}
]

Before the update to v2.3.3 swagger showed the correct expected result like:

[
  {
    "sexTypeChoices": [
      "string"
    ]
}
]

This bug was introduced with commit #2788

 if (!$propertyMetadata->isReadable() && !$propertyMetadata->isWritable()) {
    continue;
}

Since I get a correct result by calling the endpoint, the property IS readable. So either the condition is wrong or isReadable returns a wrong result. As this was working before the commit I consider this a BC break.

remoteclient commented 5 years ago

Second: The second one is similar but also a bit more complicated since this is not only about non-mapped properties but also non-mapped nested classes (properties).

I have Dog which inherits from Pet. Pet has a property called $analysis which is of type Analysis (non-mapped). Analysis holds another non-mapped property (arrayCollection) called RecipeItem. This one holds a mapped property $ingredient. So in the End Pet holds several non-mapped nested classes.:

class Pet
{
    ...
    /**
     * @var Analysis
     */
    private $analysis;

    ....
}
class Analysis
{

    /**
     * Analysis constructor.
     */
    public function __construct()
    {
        $this->recipeItems = new ArrayCollection();
        $this->nutrientTotal = new Requirement();
        $this->nutrientPercentageTotal = new Requirement();
    }

    /**
     * @var int
     */
    private $id;

    /**
     * @var Pet
     */
    private $pet;

    /**
     * @var RecipeItem[]
     */
    private $recipeItems;

    /**
     * @var Requirement
     */
    private $nutrientTotal;

    /**
     * @var Requirement
     */
    private $nutrientPercentageTotal;

    /**
     * @var RequirementSatisfaction
     */
    private $requirement;

    ...
}
class RecipeItem implements RecipeItemInterface
{
    /**
     * RecipeItem constructor.
     */
    public function __construct()
    {
        $this->nutrientTotal = new Requirement();
    }

    /**
     * @var NutritionDatabase
     */
    private $ingredient;

    /**
     * @var int
     */
    private $quantity;

    /**
     * @var string
     */
    private $comparison;

    /**
     * @var Requirement
     */
    private $nutrientTotal;
    ...
}

As Dog inherits form Pet I have a special endpoint which takes ingredients and quantity as input and outputs an analysis which shows if the ingridients of a recipe meets the requirement on nutrion for a specific pet in this case a dog.

<itemOperation name="dog_nutrition_analysis">
    <attribute name="method">PUT</attribute>
    <attribute name="access_control">is_granted('ROLE_MWS_NC_CREATE')</attribute>
    <attribute name="path">/dogs/{id}/analysis</attribute>
    <attribute name="controller">MWS\NutritionCalculatorBundle\Controller\Analysis</attribute>
    <attribute name="normalization_context">
        <attribute name="groups">
            <attribute name="group">mws_nc_dog_analysis_read</attribute>
        </attribute>
    </attribute>
    <attribute name="denormalization_context">
        <attribute name="groups">
            <attribute name="group">mws_nc_dog_analysis_write</attribute>
        </attribute>
    </attribute>
</itemOperation>

This is also giving me the correct results when I call the endpoint. Serialization groups are set properly. What is missing here is like above the expected output but also the correct body in which format and what properties to send. This behavior was introduced with v2.4.0. Expected Input shows:

{}

And the expected output shows:

{}

With v2.3.6 I get the correct expected input:

{
  "analysis": {
    "recipeItems": [
      {
        "ingredient": "string",
        "quantity": 0
      }
    ]
  }
}

and also the correct expected output:

{
  "analysis": {
    "recipeItems": [
      {
        "ingredient": "string",
        "quantity": 0,
        "nutrientTotal": {
          "TS": 0,
          "GJ": 0,
          "GCAL": 0,
          "ZE": 0,
          "ZF": 0,
          "ZK": 0,
          "ZB": 0,
          "F182": 0,
          "F183": 0,
          "F205": 0,
          "F226": 0,
          "MCA": 0,
          "MP": 0,
          "MMG": 0,
          "MNA": 0,
          "MK": 0,
          "MCL": 0,
          "MFE": 0,
          "MCU": 0,
          "MZN": 0,
          "MMN": 0,
          "MJ": 0,
          "VA": 0,
          "VD": 0,
          "VE": 0,
          "VB1": 0,
          "VB2": 0,
          "VB3": 0,
          "VB5": 0,
          "VB6": 0,
          "VB7": 0,
          "VB9G": 0,
          "VB12": 0,
          "EARG": 0,
          "EHIS": 0,
          "EILE": 0,
          "EMET": 0,
          "ECYS": 0,
          "EMETECYS": 0,
          "ELEU": 0,
          "ELYS": 0,
          "EPHE": 0,
          "EPHEETYR": 0,
          "ETHR": 0,
          "ETRP": 0,
          "EVAL": 0,
          "EP": 0,
          "GMCAMP": 0,
          "GZKTS": 0,
          "GZBTS": 0,
          "GZFTS": 0,
          "GZMTS": 0,
          "GGJDENS": 0,
          "GKAB": 0,
          "GHARNPH": 0
        }
      }
    ],
    "nutrientTotal": {
      "TS": 0,
      "GJ": 0,
      "GCAL": 0,
      "ZE": 0,
      "ZF": 0,
      "ZK": 0,
      "ZB": 0,
      "F182": 0,
      "F183": 0,
      "F205": 0,
      "F226": 0,
      "MCA": 0,
      "MP": 0,
      "MMG": 0,
      "MNA": 0,
      "MK": 0,
      "MCL": 0,
      "MFE": 0,
      "MCU": 0,
      "MZN": 0,
      "MMN": 0,
      "MJ": 0,
      "VA": 0,
      "VD": 0,
      "VE": 0,
      "VB1": 0,
      "VB2": 0,
      "VB3": 0,
      "VB5": 0,
      "VB6": 0,
      "VB7": 0,
      "VB9G": 0,
      "VB12": 0,
      "EARG": 0,
      "EHIS": 0,
      "EILE": 0,
      "EMET": 0,
      "ECYS": 0,
      "EMETECYS": 0,
      "ELEU": 0,
      "ELYS": 0,
      "EPHE": 0,
      "EPHEETYR": 0,
      "ETHR": 0,
      "ETRP": 0,
      "EVAL": 0,
      "EP": 0,
      "GMCAMP": 0,
      "GZKTS": 0,
      "GZBTS": 0,
      "GZFTS": 0,
      "GZMTS": 0,
      "GGJDENS": 0,
      "GKAB": 0,
      "GHARNPH": 0
    },
    "nutrientPercentageTotal": {
      "TS": 0,
      "GJ": 0,
      "GCAL": 0,
      "ZE": 0,
      "ZF": 0,
      "ZK": 0,
      "ZB": 0,
      "F182": 0,
      "F183": 0,
      "F205": 0,
      "F226": 0,
      "MCA": 0,
      "MP": 0,
      "MMG": 0,
      "MNA": 0,
      "MK": 0,
      "MCL": 0,
      "MFE": 0,
      "MCU": 0,
      "MZN": 0,
      "MMN": 0,
      "MJ": 0,
      "VA": 0,
      "VD": 0,
      "VE": 0,
      "VB1": 0,
      "VB2": 0,
      "VB3": 0,
      "VB5": 0,
      "VB6": 0,
      "VB7": 0,
      "VB9G": 0,
      "VB12": 0,
      "EARG": 0,
      "EHIS": 0,
      "EILE": 0,
      "EMET": 0,
      "ECYS": 0,
      "EMETECYS": 0,
      "ELEU": 0,
      "ELYS": 0,
      "EPHE": 0,
      "EPHEETYR": 0,
      "ETHR": 0,
      "ETRP": 0,
      "EVAL": 0,
      "EP": 0,
      "GMCAMP": 0,
      "GZKTS": 0,
      "GZBTS": 0,
      "GZFTS": 0,
      "GZMTS": 0,
      "GGJDENS": 0,
      "GKAB": 0,
      "GHARNPH": 0
    },
    "requirement": {
      "TS": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "GJ": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "GCAL": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "ZE": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "ZF": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "ZK": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "ZB": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "F182": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "F183": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "F205": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "F226": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "MCA": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "MP": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "MMG": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "MNA": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "MK": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "MCL": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "MFE": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "MCU": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "MZN": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "MMN": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "MJ": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "VA": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "VD": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "VE": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "VB1": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "VB2": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "VB3": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "VB5": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "VB6": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "VB7": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "VB9G": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "VB12": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "EARG": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "EHIS": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "EILE": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "EMET": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "ECYS": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "EMETECYS": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "ELEU": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "ELYS": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "EPHE": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "EPHEETYR": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "ETHR": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "ETRP": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "EVAL": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "EP": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "GMCAMP": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "GZKTS": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "GZBTS": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "GZFTS": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "GZMTS": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "GGJDENS": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "GKAB": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      },
      "GHARNPH": {
        "safeUpperLimit": 0,
        "recommendedAllowance": 0,
        "minimalRequirement": 0
      }
    }
  }
}

If I remove the Lines from #2788 the expected output is also empty, but the expected output shows:

{
  "analysis": "string"
}

So this faulty swagger output does not only come from the linked commit. I think something is messed up in the metadataFactory. Like above as this is a change in behavior I would consider this a BC break

teohhanhui commented 5 years ago

@remoteclient Do you think the proposal in api-platform/api-platform#1197 will address this? :smile:

remoteclient commented 5 years ago

@teohhanhui The proposal looks promising, as it seems that it brings back the features that where gone from v2.3 to v2.4

The points:

they should require no ID in any context they should require no IRI in any context they should require no path to resource in any context

also used to work in v2.3. I just set a fake Id. For the last two points I worked around by decorating the IriConverter like here. With this I was able to hide non mapped and/or nested entities from docs but could use serialization with groups.

remoteclient commented 4 years ago

@teohhanhui I updated to APIP 2.5.4 today. Sadly my issue isn't fixed. The swagger output stays empty.

teohhanhui commented 4 years ago

@remoteclient Can you check if #3273 (merged into master) has changed anything on this front?

remoteclient commented 4 years ago

@teohhanhui Sorry for the late reply.... Sad to say, but I don't think so. I switched the core to dev-master and could not see any difference in swagger output.

teohhanhui commented 4 years ago

Sorry for not taking a deeper look earlier. To respond to https://github.com/api-platform/core/issues/2868#issuecomment-505013132:

This seems to have to do with using a static method, which is not supported by PropertyInfo: https://github.com/symfony/symfony/pull/21331 (see also https://github.com/symfony/symfony/pull/21331#issuecomment-274462136)

teohhanhui commented 4 years ago

As for https://github.com/api-platform/core/issues/2868#issuecomment-505026740, I've sent you a PM on Symfony Slack. Let's figure this out!

remoteclient commented 4 years ago

I don't use the static methods anymore in favor of object classes.

https://github.com/api-platform/core/issues/2868#issuecomment-505026740 is more important. Whats missing here are input and output in swagger for unmapped related classes.

What I don't understand is that all is normalized/denormlized correctly by the serializer... So the serializer can "walk" across the related entities and get all properties while swagger cannot.

teohhanhui commented 4 years ago

https://github.com/api-platform/core/issues/2868#issuecomment-505026740 seems to be the same as #3146

remoteclient commented 4 years ago

I am closing this as it got meŕged in APIP v 2.5.5. There is one issue left. Swagger / OpenApi shows the example value only in swagger version2. There are some issues targeting this in the OpenApi repo.