Search code examples
phpsymfonyserializationdecoratorapi-platform.com

Decorating JsonLd ItemNormalizer


In API Platform, I am trying to decorate the service api_platform.jsonld.normalizer.item (class ApiPlatform\JsonLd\Serializer\ItemNormalizer).

I notice this is causing some strange behavior in my application. For example, I get an error Specified class App\Dto\File\FileMultipleResponseDto is not a resource class. which is not showing before setting up the decorator.

Here is my decorator :

<?php

declare(strict_types=1);

namespace App\Serializer;

use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
use Symfony\Component\Serializer\Exception\LogicException;
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
use Symfony\Component\Serializer\SerializerAwareInterface;
use Symfony\Component\Serializer\SerializerInterface;

#[AsDecorator('api_platform.jsonld.normalizer.item')]
class ItemJsonLdNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface
{

    public function __construct(
        private readonly NormalizerInterface $normalizer,
    ) {}


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

    public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool
    {
        return $this->normalizer->supportsDenormalization($data, $type, $format, $context);
    }

    public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed
    {
        return $this->normalizer->denormalize($data, $type, $format, $context);
    }

    public function hasCacheableSupportsMethod(): bool
    {
        return $this->normalizer->hasCacheableSupportsMethod();
    }

    public function supportsNormalization(mixed $data, string $format = null)
    {
        return $this->normalizer->supportsNormalization($data, $format);
    }

    public function setSerializer(SerializerInterface $serializer)
    {
        return $this->normalizer->setSerializer($serializer);
    }

}

As you can see, it's currently doing nothing appart from decorating the original service. What am I doing wrong ?


Solution

  • So, after some debugging (thanks to the bin/console debug:container normalizer command) I found out the issue : it seems that API Platform ElasticSearch component was already decorating the JsonLD ItemNormalizer (ApiPlatform\Elasticsearch\Serializer\ItemNormalizer), with a higher priority (-895). It causes some issues in the application which I did not investigate further.

    I used a higher priority than the API Platform decorator :

    <?php
    
    declare(strict_types=1);
    
    namespace App\Serializer;
    
    use ApiPlatform\Metadata\Post;
    use Symfony\Component\DependencyInjection\Attribute\AsDecorator;
    use Symfony\Component\DependencyInjection\Attribute\AutowireDecorated;
    use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
    use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
    use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
    use Symfony\Component\Serializer\SerializerAwareInterface;
    use Symfony\Component\Serializer\SerializerInterface;
    
    #[AsDecorator('api_platform.jsonld.normalizer.item', priority: -900)]
    class ItemJsonLdNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface, CacheableSupportsMethodInterface
    {
    
        public function __construct(
            #[AutowireDecorated] private readonly NormalizerInterface $decorated,
        ) {}
    
        public function normalize(mixed $object, string $format = null, array $context = []): array|string|int|float|bool|\ArrayObject|null
        {
            return $this->decorated->normalize($object, $format, $context);
        }
    
        public function supportsDenormalization(mixed $data, string $type, string $format = null, array $context = []): bool
        {
            return $this->decorated->supportsDenormalization($data, $type, $format, $context);
        }
    
        public function denormalize(mixed $data, string $type, string $format = null, array $context = []): mixed
        {
            // When denormalizing some API Platform resource,
            // we want to allow clients to send the resource identifier in POST operations.
            // By default, it's only allowed on PUT/PATCH operations.
            if ($context['operation'] instanceof Post) {
                $context['api_allow_update'] = true;
            }
    
            return $this->decorated->denormalize($data, $type, $format, $context);
        }
    
        public function hasCacheableSupportsMethod(): bool
        {
            return $this->decorated->hasCacheableSupportsMethod();
        }
    
        public function supportsNormalization(mixed $data, string $format = null)
        {
            return $this->decorated->supportsNormalization($data, $format);
        }
    
        public function setSerializer(SerializerInterface $serializer)
        {
            return $this->decorated->setSerializer($serializer);
        }
    
    }
    

    My normalizer is now properly taken into consideration for JsonLD requests. Be careful, you might have to implement a similar normalizer for Json requests as well, because they won't go into this created normalizer.

    For the reference, I was looking to fix this issue in my code : https://github.com/api-platform/api-platform/issues/343