Core: Docs - Custom Normaliser Circular Dependency

Created on 1 Aug 2018  路  3Comments  路  Source: api-platform/core

If I create a normaliser as described here:
https://api-platform.com/docs/core/content-negotiation/#writing-a-custom-normalizer

There is a circular dependency when autowire is configured.

  Circular reference detected for service "App\Serializer\CustomNormalizer", path: "App\Serializer\CustomNormalizer -> serializer -> App\Serializer\CustomNormalizer". 

Solution is to use the AbstractNormalizer typehint and define the service as decorating the item normalizer. Full example:

<?php

namespace App\Serializer;

use ApiPlatform\Core\Api\IriConverterInterface;
use ApiPlatform\Core\Serializer\AbstractItemNormalizer;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

final class CustomNormalizer implements NormalizerInterface, DenormalizerInterface
{
    private $normalizer;

    public function __construct(
        AbstractNormalizer $normalizer,
        IriConverterInterface $iriConverter
    )
    {
        if (!$normalizer instanceof DenormalizerInterface) {
            throw new \InvalidArgumentException('The normalizer must implement the DenormalizerInterface');
        }
        if(!$normalizer instanceof AbstractItemNormalizer) {
            throw new \InvalidArgumentException('The normalizer must be an instance of AbstractItemNormalizer');
        }
        $handler = function ($entity) use ($iriConverter) {
            return $iriConverter->getIriFromItem($entity);
        };
        $normalizer->setMaxDepthHandler($handler);
        $normalizer->setCircularReferenceHandler($handler);
        $this->normalizer = $normalizer;
    }

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

    public function supportsDenormalization($data, $type, $format = null)
    {
        return $this->normalizer->supportsDenormalization($data, $type, $format);
    }

    public function normalize($object, $format = null, array $context = [])
    {
        return $this->normalizer->normalize($object, $format, $context);
    }

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

yaml services config:

App\Serializer\CustomNormalizer:
        autowire: false
        autoconfigure: false
        tags: ['serializer.normalizer']
        decorates: 'api_platform.jsonld.normalizer.item'
        arguments:
            $normalizer: '@App\Serializer\CustomNormalizer.inner'
            $iriConverter: '@ApiPlatform\Core\Api\IriConverterInterface'

UPDATE:
The above results in another error
The injected serializer must be an instance of "Symfony\Component\Serializer\Normalizer\NormalizerInterface".

Having a little debug now trying to resolve this as the injected serializer is null

bug unconfirmed

All 3 comments

Final working setup:

<?php

namespace App\Serializer;

use ApiPlatform\Core\Api\IriConverterInterface;
use ApiPlatform\Core\Serializer\AbstractItemNormalizer;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

final class CustomNormalizer implements NormalizerInterface, DenormalizerInterface
{
    private $normalizer;

    public function __construct(
        NormalizerInterface $normalizer,
        IriConverterInterface $iriConverter
    )
    {
        if (!$normalizer instanceof DenormalizerInterface) {
            throw new \InvalidArgumentException('The normalizer must implement the DenormalizerInterface');
        }
        if(!$normalizer instanceof AbstractItemNormalizer) {
            throw new \InvalidArgumentException('The normalizer must be an instance of AbstractItemNormalizer');
        }
        $handler = function ($entity) use ($iriConverter) {
            return $iriConverter->getIriFromItem($entity);
        };
        $normalizer->setMaxDepthHandler($handler);
        $normalizer->setCircularReferenceHandler($handler);
        $this->normalizer = $normalizer;
    }

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

    public function supportsDenormalization($data, $type, $format = null)
    {
        return $this->normalizer->supportsDenormalization($data, $type, $format);
    }

    public function normalize($object, $format = null, array $context = [])
    {
        return $this->normalizer->normalize($object, $format, $context);
    }

    public function supportsNormalization($data, $format = null)
    {
        return $this->normalizer->supportsNormalization($data, $format);
    }
}
App\Serializer\CustomNormalizer:
        autowire: false
        autoconfigure: true
        arguments:
            $normalizer: '@api_platform.jsonld.normalizer.item'
            $iriConverter: '@ApiPlatform\Core\Api\IriConverterInterface'

The only difference in the docs and what works seems to be in the config. I'm having to inject the API Platform normalizer rather than what is automatically injected using the NormalizerInterface typehint

You must implements SerializerAwareInterface and use the SerializerAwareTrait:

```
final class MyNormalizer implements NormalizerInterface, DenormalizerInterface,
SerializerAwareInterface
{

use SerializerAwareTrait;
/**
 * @var DenormalizerInterface|NormalizerInterface
 */
private $normalizer;

public function setSerializer(SerializerInterface $serializer)
{
    if($this->normalizer instanceof SerializerAwareInterface) {
        $this->normalizer->setSerializer($serializer);
    }
}

```

I'm reviewing my older open issues - I think this will certainly have been the issue. I'll re-open this issue when I take a look again if it is indeed a bug. I'd have thought some tests or other users would have picked this up though by now if it wasn't me doing something wrong

Was this page helpful?
0 / 5 - 0 ratings