Search code examples
circular-dependencysymfony4

Symfony4 service container circular dependency


In the process of migrating a symfony3.4 project to Symfony4 with symfony-flex

After adapting the directory structure and proceeding to few adjustments, I now face what looks like a circular dependency error that comes to light during the application service container bootstrapping phase. Hitting the application (whether from the console or the fronted) raises some exception related to Xdebug saying «nested function calls limit reached ('256')».

Looking at the backtrace reveals kind of a pattern that repeats itself and leads to the conclusion that Xdebug is not the one to blame but two services depending indirectly on each-other instead.

Involved services are:

  • Log manager is a public service which helps persisting user actions to database:

    log_manager:
        class: Service\Log\LogManager
        public: true
        arguments:
            - "@doctrine.orm.entity_manager"
            - "@?security.token_storage"
    
  • LoggableSubscriber consumes LogManager service to create a new record when some application objects state change:

    Doctrine\Behavior\ORM\Loggable\LoggableSubscriber:
        public: false
        arguments:
            - "@log_manager"
            - "@event_dispatcher"
        tags:
            - { name: doctrine.event_subscriber }
    

Backtrace (please read from bottom to top):

==== ↑↑ PATTERN REPEATS ON AND ON ↑↑ ====
==== ONCE AGAIN DOCTRINE LOOKS FOR SUBSCRIBERS ====
at boDevDebugProjectContainer->load('…/var/cache/bo/dev/ContainerTv5AUyL/getLoggableSubscriberService.php')
    in getDoctrine_Dbal_ApiacmeConnectionService.php (line 34)
at require('…/var/cache/bo/dev/ContainerTv5AUyL/getDoctrine_Dbal_ApiacmeConnectionService.php')
    in boDevDebugProjectContainer.php (line 446)
at boDevDebugProjectContainer->load('…/var/cache/bo/dev/ContainerTv5AUyL/getDoctrine_Dbal_ApiacmeConnectionService.php')
    in getDoctrine_Orm_ApiacmeEntityManagerService.php (line 74)
at require('…/var/cache/bo/dev/ContainerTv5AUyL/getDoctrine_Orm_ApiacmeEntityManagerService.php')
    in boDevDebugProjectContainer.php (line 446)
==== "LOGMANAGER" NATURALLY REQUIRES DOCTRINE ENTITY MANAGER SERVICE ====
at boDevDebugProjectContainer->load('…/var/cache/bo/dev/ContainerTv5AUyL/getDoctrine_Orm_ApiacmeEntityManagerService.php')
    in getLogManagerService.php (line 10)
at require('…/var/cache/bo/dev/ContainerTv5AUyL/getLogManagerService.php')
    in boDevDebugProjectContainer.php (line 446)
at boDevDebugProjectContainer->load('…/var/cache/bo/dev/ContainerTv5AUyL/getLogManagerService.php')
    in getLoggableSubscriberService.php (line 11)

==== BUT OUR "LOGGABLESUBSCRIBER" IN TURN NEEDS THE "LOGMANAGER" SERVICE ====
at require('…/var/cache/bo/dev/ContainerTv5AUyL/getLoggableSubscriberService.php')
    in boDevDebugProjectContainer.php (line 446)

==== DOCTRINE LOADS SUBSCRIBERS (ATTACHED VIA "TAG" PROPERTY) ====
at boDevDebugProjectContainer->load('…/var/cache/bo/dev/ContainerTv5AUyL/getLoggableSubscriberService.php')
    in getDoctrine_Dbal_ApiacmeConnectionService.php (line 34)
==== ↑↑ PATTERN STARTS HERE ↑↑ ====

at require('…/var/cache/bo/dev/ContainerTv5AUyL/getDoctrine_Dbal_ApiacmeConnectionService.php')
    in boDevDebugProjectContainer.php (line 446)
at boDevDebugProjectContainer->load('…/var/cache/bo/dev/ContainerTv5AUyL/getDoctrine_Dbal_ApiacmeConnectionService.php')
    in getDoctrine_Orm_ApiacmeEntityManagerService.php (line 74)
at require('…/var/cache/bo/dev/ContainerTv5AUyL/getDoctrine_Orm_ApiacmeEntityManagerService.php')
    in boDevDebugProjectContainer.php (line 446)

==== FIRST, OUR "LOGMANAGER" SERVICE DEPENDS ON DOCTRINE MAIN ENTITYMANAGER ====
at boDevDebugProjectContainer->load('…/var/cache/bo/dev/ContainerTv5AUyL/getDoctrine_Orm_ApiacmeEntityManagerService.php')
    in getLogManagerService.php (line 10)
at require('…/var/cache/bo/dev/ContainerTv5AUyL/getLogManagerService.php')
    in boDevDebugProjectContainer.php (line 446)
↑↑ ↑↑ ↑↑
…

Is this purely a misconception issue ? Or is there a way to make the service container aware of this situation by setting some property/flag somewhere in the configuration so that it doesn't go mad ? I saw several questions related to this topic but answers are specific to their question's context.

I was about to break the LogManager service dependency on doctrine's entity manager, making LogManager class unable to persist its underlying entity object to database by itself anymore and as a consequence delegating that job to the calling context by returning the instance to be persisted instead. I am not convinced this is the ideal solution though.

Suggested approach would really be appreciated.
Thank you.


Solution

  • Replacing dependency injection argument with a method call did fix the issue. My log_manager service definition now looks like this:

    log_manager:
        class: Service\Log\LogManager
        public: true
        arguments:
            - "@?security.token_storage"
        calls:
            - ['setEntityManager', ["@doctrine.orm.entity_manager"]]
    

    Please comment/answer if there is a better way to solve the issue.