Double-Nested Subresource Configuration
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ł