Core: CircularReferenceException in custom ItemDataProvider

Created on 8 Jun 2017  Â·  20Comments  Â·  Source: api-platform/core

Hello,

I'm implementing 2 custom data providers, connecting on WordPress API: 1 for collection, 1 for item. For the first one, I can easily inject the serializer service. But for the item, I get a CircularReferenceException.

A solution would be to create an event subscriber which listens to kernel.request event and serialize the controller result, but it's not generic at all (and I don't find it very clean…).

A better solution would be to listen to SerializerAwareInterface on my ItemDataProvider from API Platform, and if so call setSerializer method.

What do you think of it @api-platform/core-team?

question

Most helpful comment

I need to serialize this list to an array of AppBundle\WordPressPost objects.

Do you mean deserializing the JSON data into the Post objects? I guess it makes sense then...

A better solution would be to listen to SerializerAwareInterface on my ItemDataProvider from API Platform, and if so call setSerializer method.

Or some other tricks like lazy services which should help...

All 20 comments

A data provider isn't supposed to do serialization?

@teohhanhui So how can I convert my WordPress API JSON result to my entity?

Just to understand the context you're parsing the resulting JSON (from WP API) and then populating some entities?

@soyuka Yep. I have a PostCollectionDataProvider which calls WordPress API (through guzzle) to get the list of posts in JSON, I need to serialize this list to an array of AppBundle\WordPress\Post objects. Same for PostItemDataProvider

Then, I'll have the same context with Tag & Category objects

I need to serialize this list to an array of AppBundle\WordPressPost objects.

Do you mean deserializing the JSON data into the Post objects? I guess it makes sense then...

A better solution would be to listen to SerializerAwareInterface on my ItemDataProvider from API Platform, and if so call setSerializer method.

Or some other tricks like lazy services which should help...

to deserialize then not serialize :p.

I don't get the issue here, you should be able to use your own serializer/normalizer.

To deserialize of course, my bad 😉

Or some other tricks like lazy services which should help...

@teohhanhui Like a service locator?

# services.yml
services:
    app.serializer.locator:
        class: Symfony\Component\DependencyInjection\ServiceLocator
        arguments:
            -
                serializer: '@serializer'
        tags:
            - { name: container.service_locator }

    App\DataProvider\PostItemDataProvider:
        class: App\DataProvider\PostItemDataProvider
        arguments: ['@csa_guzzle.client.word_press', '@app.serializer.locator']
        tags:
            - { name: api_platform.item_data_provider }
// PostItemDataProvider.php
class PostItemDataProvider implements ItemDataProviderInterface
{
    private $client;
    private $container;

    /**
     * @param Client $client
     */
    public function __construct(Client $client, ContainerInterface $container)
    {
        $this->client = $client;
        $this->container = $container;
    }

    /**
     * {@inheritdoc}
     */
    public function getItem(string $resourceClass, $id, string $operationName = null, array $context = [])
    {
        if (Post::class !== $resourceClass) {
            throw new ResourceClassNotSupportedException();
        }

        try {
            return $this->container->get('serializer')->deserialize($this->client->get('wp-json/wp/v2/posts/'.$id)->getBody(), Post::class, 'json');
        } catch (RequestException $exception) {
            return null;
        }
    }
}

I don't like the service locator, but whatever works for you... :smile:

I'm listening to any better (and cleaner) solution 😃

Does the SerializerAwareInterface not work well for this case? :D

Just implementing it does not do the trick. I must inject the serializer in ReadListener and other services, which will call setSerializer (from SerializerAwareTrait) in ChainItemDataProvider, which will call the same method on each ItemDataProvider if it implements SerializerAwareInterface.

Not sure it's the right solution… But I think it should really help cause deserialization in a custom ItemDataProvider will happen many times

You could do that using a compiler pass? Oh, nevermind... I think that will still result in a CircularReferenceException. Some laziness needs to be added somewhere.

I'm not convinced that your typical data providers should need the serializer, but I think there's not really any harm if we do that...

You could do that using a compiler pass?

You mean adding a call on the definition? It will still cause the circular exception. That's why I need to set it out of container compilation (that's what the serializer does).

@teohhanhui @soyuka So what do you think about the SerializerAwareInterface solution? Do you think it should be done in API Platform as generic, or is it just specific to my projects (I have 2 projects with the same issue)?

@vincentchalamon IMO it could be done in api-platform

Why not, might help some. If I understood this correctly you want the data providers to implement the SerializerAwareInterface? I'd clearly see an interest in this.

@soyuka that's the main idea, but we should first inject the serializer in services which call ItemDataProvider::getItem, to inject it like the serializer does: https://github.com/symfony/serializer/blob/master/Serializer.php#L60

PR has been merged on November and may be released on 2.2

Was this page helpful?
0 / 5 - 0 ratings