api-platform / core

The server component of API Platform: hypermedia and GraphQL APIs in minutes
https://api-platform.com
MIT License
2.44k stars 869 forks source link

ApiPlatform causes a service circular reference for @serializer #1091

Closed dkarlovi closed 6 years ago

dkarlovi commented 7 years ago

I'm using Enqueue and am trying to register a Doctrine event processor which will convert Doctrine events to RabbitMQ messages, this would enable me to register remote (sync/async) processes as listeners of Doctrine events as easy as tagging my processors.

Anyway, I wish to (de)serialize the entity currently being processed (via @serializer), service, but:

app.queue.test_processor:
    class: 'AppBundle\Queue\TestProcessor'
    arguments:
        - '@enqueue.producer'
        - '@serializer'
    tags:
        # used by worker
        - { name: enqueue.client.processor }
        # used by app
        - { name: doctrine.event_listener, event: prePersist }
        - { name: doctrine.event_listener, event: postPersist }
        - { name: doctrine.event_listener, event: preUpdate }
        - { name: doctrine.event_listener, event: postUpdate }
        - { name: doctrine.event_listener, event: preRemove }
        - { name: doctrine.event_listener, event: postRemove }

Error:

Circular reference detected for service "doctrine.dbal.default_connection", path: "cache_warmer -> twig -> security.authorization_checker -> fos_oauth_server.server -> fos_oauth_server.storage -> fos_oauth_server.client_manager.default -> doctrine.dbal.default_connection -> app.queue.test_processor -> serializer -> api_platform.metadata.property.name_collection_factory -> property_info -> doctrine.orm.default_entity_manager.property_info_extractor".

If I disable ApiPlatform bundle, it works as expected (but, obviously, without the AP features). :) Am I doing something wrong here?

dunglas commented 7 years ago

Hi @dkarlovi,

You have 2 solutions to avoid this circular reference:

  1. Create another instance of the serializer for your listeners
  2. Inject the container in AppBundle\Queue\TestProcessor (not recommended, but with Symfony 3.3 you can use PSR-11 service locators instead).
dkarlovi commented 7 years ago

@dunglas this is a known issue then?

With 2), I get('serializer') in my processor instead?

dunglas commented 7 years ago

This not really a known issue. The issue is not related to API Platform nor to Enqueue directly. It's the use of both together that create this problem. Circular references are hard to avoid when a lot of services depend of each other.

About 2), yes.

dkarlovi commented 7 years ago

The problem here might be @serializer (which is a "system service") depends on @api_platform.metadata.property.name_collection_factory if ApiPlatform is installed (if I'm reading this right), but does not if it's not installed. I understand why this is, but IMO you should try to avoid overriding system services if at all possible because integration with other bundles might be much harder (bundle authors will test with bare Symfony, probably not with Symfony+AP).

OK, will try to work around it, thank you for your quick response and help. Closing.

dunglas commented 7 years ago

The serializer service is not overriden. We register new normalizers to the global serializer (it allows to use native Symfony normalizers such as the data: URI or the DateTimeNormalizer ones).

We may create a new instance of the Symfony serializer instead, it has been discussed several times. It has pro and cons (it's harder to register custom normalizers for the user for instance).

teohhanhui commented 7 years ago

I think it makes sense for a job to use the container, just like the controller. If you want to avoid that, there are many other options including setter injection.

dkarlovi commented 7 years ago

I've dropped the idea of handling preXXX events so serializing the complete entity is no longer needed, switched to plain json_encode() if the indentifier. Thanks for your help @dunglas @teohhanhui

dkarlovi commented 6 years ago

Reopened as some discussion happened here before: if I follow the steps for using the serializer with FOS Elastica, I get the same error:

  [Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException]  
  Circular reference detected for service "doctrine.dbal.default_connection",          
   path: "doctrine.dbal.default_connection -> fos_elastica.object_persister.a          
  pp.image -> fos_elastica.index.app.image -> serializer -> api_platform.item          
  _data_provider".  

In this case I can't work around that with using the service locator as it's vendor code which might be quite tricky to handle.

Simperfit commented 6 years ago

@dkarlovi is it still an issue ?

dkarlovi commented 6 years ago

Not that I know of.

erikkubica commented 4 years ago

@dunglas issue comes from using FQDN as service name vs defining custom servise name and defining the class as argument.

This is not working (circular reference error):

brandfusion.serializer.normalizer.api:
        class: App\Serializer\ApiNormalizer
        decorates: 'api_platform.serializer.normalizer.item'
        arguments: [ '@App\Serializer\ApiNormalizer.inner','@doctrine.orm.entity_manager']

OR

brandfusion.serializer.normalizer.api:
        class: App\Serializer\ApiNormalizer
        decorates: 'api_platform.serializer.normalizer.item'
        arguments: [ '@brandfusion.serializer.normalizer.api.inner','@doctrine.orm.entity_manager']

This is working:

'App\Serializer\ApiNormalizer':
        decorates: 'api_platform.serializer.normalizer.item'
        arguments: [ '@App\Serializer\ApiNormalizer.inner','@doctrine.orm.entity_manager']

------ EDIT ------

Figured out what was my issue. Serializer folder was not excluded from auto registering as service, as soon I have excluded it and edited the code above to:

    brandfusion.normalizer.serializer.json:
        class: App\Serializer\ApiNormalizer
        decorates: 'api_platform.serializer.normalizer.item'
        arguments: [ '@brandfusion.normalizer.serializer.json.inner' ,'@doctrine.orm.entity_manager']

    brandfusion.normalizer.serializer.jsonld:
        class: App\Serializer\ApiNormalizer
        decorates: 'api_platform.jsonld.normalizer.item'
        arguments: [ '@brandfusion.normalizer.serializer.jsonld.inner' ,'@doctrine.orm.entity_manager']

Than it works as expected, I am not 100% sure why tho.

In both cases I am creating two services based on the same class, just with different decorates attribute. only difference is that App\Serializer\ApiNormalizer is not auto-registered