api-platform / core

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

Double-Nested Subresource Configuration #6604

Open Rafikooo opened 2 months ago

Rafikooo commented 2 months ago

API Platform version(s) affected: 3.3.11

Description
While upgrading the Sylius API to APIP3, we faced an issue with subresource configuration. Here’s a specific real-life example to illustrate the problem: https://syliusdev-demoapip3.bunnyenv.com/api/v2#/ProductOption We have upgraded this resource to APIP3 with a minor workaround:

Current behaviour (result of the POST request):

{
    "@context": "\/api\/v2\/contexts\/ProductOption",
    "@id": "\/api\/v2\/admin\/product-options\/some_option",
    "@type": "ProductOption",
    "code": "some_option",
    "position": 16,
    "values": [
        {
            "@id": "\/api\/v2\/admin\/product-options\/some_option\/values\/some_value",
            "@type": "ProductOptionValue",
            "code": "some_value",
            "translations": {
                "en_US": {
                    "@id": "\/api\/v2\/admin\/product-option-values\/some_value\/translations\/en_US",
                    "@type": "ProductOptionValueTranslation",
                    "value": "Translation for the resource's value"
                }
            },
            "value": "Translation for the resource's value"
        }
    ],
    "createdAt": "2024-08-14 10:23:05",
    "updatedAt": "2024-08-14 10:23:05",
    "translations": {
        "en_US": {
            "@id": "\/api\/v2\/admin\/product-options\/some_option\/translations\/en_US",
            "@type": "ProductOptionTranslation",
            "name": "Translation for the main resource"
        }
    },
    "name": "Translation for the main resource"
}

Current subresource's generated IRI:

{"@id": "\/api\/v2\/admin\/product-option-values\/some_value\/translations\/en_US"},

Expected one:

{"@id": "\/api\/v2\/admin\/product-options\/some_option\/values\/some_value\/translations\/en_US"},

Our workaround requires defining only two uriVariables:

<resource class="Sylius\Component\Product\Model\ProductOptionValueTranslation">
    <operations>
        <operation class="ApiPlatform\Metadata\NotExposed"
                   uriTemplate="/admin/product-option-values/{code}/translations/{localeCode}">
            <uriVariables>
                <uriVariable parameterName="code" fromClass="Sylius\Component\Product\Model\ProductOptionValue" fromProperty="translations" />
                <uriVariable parameterName="localeCode" fromClass="Sylius\Component\Product\Model\ProductOptionValueTranslation" />
            </uriVariables>
        </operation>
    </operations>
</resource>

The documentation does not cover this case, but we experimented with the configuration and successfully generated the expected IRI:

<resource class="Sylius\Component\Product\Model\ProductOptionValueTranslation">
    <operations>
        <operation class="ApiPlatform\Metadata\NotExposed"
                   uriTemplate="/admin/product-options/{optionCode}/values/{valueCode}/translations/{localeCode}">
            <uriVariables>
                <uriVariable parameterName="optionCode" fromClass="Sylius\Component\Product\Model\ProductOption" toProperty="translatable.option" />
                <uriVariable parameterName="valueCode" fromClass="Sylius\Component\Product\Model\ProductOptionValue" fromProperty="translations" />
                <uriVariable parameterName="localeCode" fromClass="Sylius\Component\Product\Model\ProductOptionValueTranslation" />
            </uriVariables>
        </operation>
    </operations>
</resource>

We access the root resource through the middle resource using the toProperty="translatable.option" attribute, which works well for GET requests. However, it fails for updates, resulting in the following error:

{
    "@context": "\/api\/v2\/contexts\/Error",
    "@type": "hydra:Error",
    "hydra:title": "An error occurred",
    "hydra:description": "[Semantical Error] line 0, col 92 near '.translatable.option': Error: Identification Variable o_a2 used in join path expression but was not defined before."
}

We have a similar case with our Order <-> OrderItem <-> OrderItemUnit resource configuration, as shown in the following config:

    <resource class="Sylius\Component\Core\Model\OrderItemUnit">
        <operations>
            <operation class="ApiPlatform\Metadata\Get" uriTemplate="/admin/orders/{tokenValue}/items/{itemId}/units/{unitId}">
                <uriVariables>
                    <uriVariable parameterName="tokenValue" fromClass="Sylius\Component\Core\Model\Order" toProperty="orderItem.order"/>
                    <uriVariable parameterName="itemId" fromClass="Sylius\Component\Core\Model\OrderItem" toProperty="orderItem"/>
                    <uriVariable parameterName="unitId" fromClass="Sylius\Component\Core\Model\OrderItemUnit"/>
                </uriVariables>
            </operation>
        </operations>
    </resource>

We can successfully construct an IRI for a double-nested resource:

{
    "@context": "\/api\/v2\/contexts\/Order",
    "@id": "\/api\/v2\/admin\/orders\/some_token",
    "@type": "Order",
    "tokenValue": "some_token",
    "items": [
        {
            "@id": "\/api\/v2\/admin\/orders\/some_token\/items\/56",
            "@type": "OrderItem",
            "units": [
                "\/api\/v2\/admin\/orders\/some_token\/items\/56\/units\/191",
                "\/api\/v2\/admin\/orders\/some_token\/items\/56\/units\/192",
                "\/api\/v2\/admin\/orders\/some_token\/items\/56\/units\/193"
            ]
        }
    ]
}

But, we cannot access the OrderItemUnit resource directly because of the following error:

{
    "@context": "\/api\/v2\/contexts\/Error",
    "@type": "hydra:Error",
    "hydra:title": "An error occurred",
    "hydra:description": "[Semantical Error] line 0, col 111 near '.order o_a2 WHERE': Error: Class Sylius\\Component\\Core\\Model\\OrderItem has no association named orderItem"
} 

We couldn't find a fully functional solution, so we decided to ask you this question:

Are double-nested subresource scenarios possible with APIP3? Did you consider these cases during subresource design? If so, please offer guidance on configuration or expand the documentation for current and future users.

Best regards, Rafał

g-ra commented 3 weeks ago

i think its related with https://github.com/api-platform/core/issues/6522

er1z commented 1 week ago

Any plans on this? I currently struggle with such an issue and it's really painful.

soyuka commented 5 days ago

https://api-platform.com/docs/guides/handle-links/ should make this work quite easily no?

er1z commented 4 days ago

@soyuka somewhat it helped. But I guess it should be available out-of-the-box, maybe if toProperty: 'entity.subentity', is dot separated, then do adequate joins?