php - APIPlatform v2.73.0 - Custom Patch action - Wrong iri - Stack Overflow

I have to migrate an API developed on APIPlatform v2 to APIPlatform v3.In this project the previous de

I have to migrate an API developed on APIPlatform v2 to APIPlatform v3. In this project the previous developer created custom patch actions as this (in a YAML file) :

    reactivated:
      security_post_denormalize: 'is_granted("OT", object)'
      security_post_denormalize_message: "Vous n'avez pas l'accès à cette ressource."
      method: PATCH
      path: /o_ts/{id}/reactivated
      controller: App\Controller\OTReactivated
      openapi_context:
        summary: Work Order reactivated

I transformed it to PHP 8 attribute like this :

#[ApiResource(
    operations: [
        new GetCollection(normalizationContext: ['groups' => ['ot_collection_read']]),
        new Get(requirements: ['id' => '\d+'], normalizationContext: ['groups' => ['ot_read']]),
        new Post(
            securityPostDenormalize: 'is_granted("OT", object)',
            securityPostDenormalizeMessage: "Vous n'avez pas l'accès à cette ressource.",
            validationContext: ['groups' => [OTAddSequencedGroup::class]],
        ),
        new Put(
            denormalizationContext: ['groups' => ['ot_update_write', 'ot:attachments', 'attachment:write', 'attachment:subcontractingProductionCenter', 'attachment:customFieldValues', 'attachment_custom_field_value:field', 'custom_field_value:write']],
            securityPostDenormalize: 'is_granted("OT", object)',
            securityPostDenormalizeMessage: "Vous n'avez pas l'accès à cette ressource.",
            validationContext: ['groups' => ['Default', OTDefaultSequencedGroup::class, 'attachment:valid:create', 'ot:address:update']]
        ),
        new Patch(
            requirements: ['id' => '\d+'],
            denormalizationContext: ['groups' => ['ot_update_write', 'ot:attachments', 'attachment:write', 'attachment:subcontractingProductionCenter', 'attachment:customFieldValues', 'attachment_custom_field_value:field', 'custom_field_value:write']],
            securityPostDenormalize: 'is_granted("OT", object)',
            securityPostDenormalizeMessage: "Vous n'avez pas l'accès à cette ressource.",
            validationContext: ['groups' => ['Default', OTDefaultSequencedGroup::class, 'attachment:valid:create', 'ot:address:update']]
        ),
    ],
    normalizationContext: ['groups' => ['ot_read']],
    denormalizationContext: ['groups' => ['ot_write']],
    validationContext: ['groups' => OTDefaultSequencedGroup::class],
    security: 'is_granted("ROLE_USER")'
)]
#[Patch(
    uriTemplate: '/o_ts/{id}/reactivated',
    controller: OTReactivated::class,
    openapiContext: ['summary' => 'Work Order reactivated'],
    securityPostDenormalize: 'is_granted("OT", object)',
    securityPostDenormalizeMessage: "Vous n'avez pas l'accès à cette ressource.",
    name: 'api_o_ts_reactivated',
)]

So far so good, the route is here, and the call work as expected. Except in the response. The generated iri is not correct. It seems that the ApiPlatform converter thinks the object id is "-id-/reactivated", but its not. Look yourself.
The call (image) :

The beginning of the response:

{
    "@context": "\/contexts\/OT",
    "@id": "\/o_ts\/2\/reactivated",
    "@type": "OT",
    "id": 2
}

The @id should be : "/o_ts/2"

I know, the documentation says that it's not recommanded but the API is already done and in production … So I can't just change the routes …

So, does anybody have a solution to my problem?

I tried to add uriVariables like this :

#[Patch(
    uriTemplate: '/o_ts/{id}/reactivated',
    uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'])],
    controller: OTClose::class,
    openapiContext: ['summary' => 'Work Order reactivated'],
    securityPostDenormalize: 'is_granted("OT", object)',
    securityPostDenormalizeMessage: "Vous n'avez pas l'accès à cette ressource.",
    name: 'api_o_ts_reactivated',
)]

I have to migrate an API developed on APIPlatform v2 to APIPlatform v3. In this project the previous developer created custom patch actions as this (in a YAML file) :

    reactivated:
      security_post_denormalize: 'is_granted("OT", object)'
      security_post_denormalize_message: "Vous n'avez pas l'accès à cette ressource."
      method: PATCH
      path: /o_ts/{id}/reactivated
      controller: App\Controller\OTReactivated
      openapi_context:
        summary: Work Order reactivated

I transformed it to PHP 8 attribute like this :

#[ApiResource(
    operations: [
        new GetCollection(normalizationContext: ['groups' => ['ot_collection_read']]),
        new Get(requirements: ['id' => '\d+'], normalizationContext: ['groups' => ['ot_read']]),
        new Post(
            securityPostDenormalize: 'is_granted("OT", object)',
            securityPostDenormalizeMessage: "Vous n'avez pas l'accès à cette ressource.",
            validationContext: ['groups' => [OTAddSequencedGroup::class]],
        ),
        new Put(
            denormalizationContext: ['groups' => ['ot_update_write', 'ot:attachments', 'attachment:write', 'attachment:subcontractingProductionCenter', 'attachment:customFieldValues', 'attachment_custom_field_value:field', 'custom_field_value:write']],
            securityPostDenormalize: 'is_granted("OT", object)',
            securityPostDenormalizeMessage: "Vous n'avez pas l'accès à cette ressource.",
            validationContext: ['groups' => ['Default', OTDefaultSequencedGroup::class, 'attachment:valid:create', 'ot:address:update']]
        ),
        new Patch(
            requirements: ['id' => '\d+'],
            denormalizationContext: ['groups' => ['ot_update_write', 'ot:attachments', 'attachment:write', 'attachment:subcontractingProductionCenter', 'attachment:customFieldValues', 'attachment_custom_field_value:field', 'custom_field_value:write']],
            securityPostDenormalize: 'is_granted("OT", object)',
            securityPostDenormalizeMessage: "Vous n'avez pas l'accès à cette ressource.",
            validationContext: ['groups' => ['Default', OTDefaultSequencedGroup::class, 'attachment:valid:create', 'ot:address:update']]
        ),
    ],
    normalizationContext: ['groups' => ['ot_read']],
    denormalizationContext: ['groups' => ['ot_write']],
    validationContext: ['groups' => OTDefaultSequencedGroup::class],
    security: 'is_granted("ROLE_USER")'
)]
#[Patch(
    uriTemplate: '/o_ts/{id}/reactivated',
    controller: OTReactivated::class,
    openapiContext: ['summary' => 'Work Order reactivated'],
    securityPostDenormalize: 'is_granted("OT", object)',
    securityPostDenormalizeMessage: "Vous n'avez pas l'accès à cette ressource.",
    name: 'api_o_ts_reactivated',
)]

So far so good, the route is here, and the call work as expected. Except in the response. The generated iri is not correct. It seems that the ApiPlatform converter thinks the object id is "-id-/reactivated", but its not. Look yourself.
The call (image) :

The beginning of the response:

{
    "@context": "\/contexts\/OT",
    "@id": "\/o_ts\/2\/reactivated",
    "@type": "OT",
    "id": 2
}

The @id should be : "/o_ts/2"

I know, the documentation says that it's not recommanded but the API is already done and in production … So I can't just change the routes …

So, does anybody have a solution to my problem?

I tried to add uriVariables like this :

#[Patch(
    uriTemplate: '/o_ts/{id}/reactivated',
    uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'])],
    controller: OTClose::class,
    openapiContext: ['summary' => 'Work Order reactivated'],
    securityPostDenormalize: 'is_granted("OT", object)',
    securityPostDenormalizeMessage: "Vous n'avez pas l'accès à cette ressource.",
    name: 'api_o_ts_reactivated',
)]
Share Improve this question edited Mar 10 at 7:27 Bruno Desprez asked Mar 8 at 19:03 Bruno DesprezBruno Desprez 12 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

So, I think I found the cause of my problem.
Everything is in the ApiPlatform\JsonLd\Serializer\ItemNormalizer.
Indeed, since v2.7 (or 3.0) the @id parameter is generated thanks to the iriConverter with the context operation passed to the method.
The operation is a Patch operation and the iriConverter take the input iri as @id.

To fix this, I made a custom normalizer which decorates the JsonLd one. Like this :

<?php

namespace App\Normalizer;

use ApiPlatform\Api\IriConverterInterface;
use ApiPlatform\Metadata\Operation;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

class CustomNormalizer implements NormalizerInterface
{
    private NormalizerInterface $decorated;

    public function __construct(private IriConverterInterface $iriConverter, private NormalizerInterface $decorated)
    {
    }

    public function normalize($object, ?string $format = null, array $context = []): null|array|\ArrayObject|bool|float|int|string
    {
        $normalizedData = $this->decorated->normalize($object, $format, $context);

        if (self::isCustomOperation($normalizedData, $context)) {
            $normalizedData = $this->regenerateIdWithIriFromGetOperation($object, $context, $normalizedData);
        }

        return $normalizedData;
    }

    public function supportsNormalization($data, ?string $format = null): bool
    {
        return $this->decorated->supportsNormalization($data, $format);
    }

    private function regenerateIdWithIriFromGetOperation($object, array $context, array $normalizedData): array
    {
        // We force the converter to use the GET operation instead of the called operation to generate the iri
        $iri = $this->iriConverter->getIriFromResource($object, context: $context);
        $normalizedData['@id'] = $iri;

        return $normalizedData;
    }

    private static function isCustomOperation($normalizedData, array $context): bool
    {
        if (!is_array($normalizedData) || !array_key_exists('@id', $normalizedData)) {
            return false;
        }
        if (!($context['operation'] ?? null) instanceof Operation) {
            return false;
        }
        $extraProps = $context['operation']->getExtraProperties();

        return $extraProps['custom_operation'] ?? false;
    }
}

Then, I added the custom normalizer in the YAML configuration (config/services.yaml) :

  App\Normalizer\CustomNormalizer:
    decorates: "api_platform.jsonld.normalizer.item"

And for the given operations, I added an extra property to only activate the custom normalizer on specific operations:

#[Patch(
    uriTemplate: '/o_ts/{id}/reactivated',
    uriVariables: ['id' => new Link(fromClass: self::class, identifiers: ['id'])],
    controller: OTReactivated::class,
    openapiContext: ['summary' => 'Work Order reactivated'],
    securityPostDenormalize: 'is_granted("OT", object)',
    securityPostDenormalizeMessage: "Vous n'avez pas l'accès à cette ressource.",
    name: 'api_o_ts_reactivated',
    extraProperties: ['custom_operation' => true],
)]

With that, the @id returned is the correct one!

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744888406a4599255.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信