79497911

Date: 2025-03-10 11:58:58
Score: 0.5
Natty:
Report link

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!

Reasons:
  • Blacklisted phrase (0.5): thanks
  • Long answer (-1):
  • Has code block (-0.5):
  • Self-answer (0.5):
  • Low reputation (1):
Posted by: Bruno Desprez