symfony: [DI] Broken entity manager service

Symfony version(s) affected: 4.2.3

Updating symfony/dependency-injection (v4.2.2 => v4.2.3)

Somehow there are now 2 entity manager instances in my application, causing all sort of WTFs 😃

See https://github.com/ro0NL/symfony-issue-30091

Before:

DefaultController.php on line 13:
EntityAwareFactory {#278 ā–¼
...
  -em: EntityManager {#233 …11}
}
DefaultController.php on line 13:
UserEmailRepository {#212 ā–¼
...
  -em: EntityManager {#233 …11}
...
}

Both entity managers are instance 233

After

DefaultController.php on line 13:
EntityAwareFactory {#278 ā–¼
...
  -em: EntityManager {#233 …11}
}
DefaultController.php on line 13:
UserEmailRepository {#298 ā–¼
...
  -em: EntityManager {#283 …11}
...
}

Now the instances differ. The only differenence is sf/di 4.2.2 vs 4.2.3. Respectively the first and last commit.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 8
  • Comments: 46 (33 by maintainers)

Commits related to this issue

Most upvoted comments

Symfony 4.2.4 was just released and I can confirm the this horrible bug still exists. Getting two different EntityManagers when using constructor injection in two different services (in my case a controller and an event listener). Downgrading to symfony/dependency-injection": ā€œ4.2.2ā€ solves it temporarily

I wonder why not more people report here, I guess this breaks a lot of applications…

A lot of WTF indeed…

Fix is now merged, please report back if you still encounter the issue (and open a new issue with a reproducer please if that happens.)

Any progress on this one? The bugs this causes are very difficult to relate to this issue, I guess thats why it is so silent here.

Here is a case that is reproduce-able:

  1. Create project using composer composer create-project symfony/website-skeleton diTest
  2. Update composer.json to lock symfony/dependency-injection to 4.2.2 and run composer update
  3. Create 2 entities, one with at Many to one relationship to the other.
  4. Use maker to create CRUD for both entities
  5. Create a service and inject the EM in the constructor, this service should create a clone of the entity and persist and flush.
  6. Create a Doctrine Event Subscriber, inject your created service.
  7. Use the created cruds to create one of each entity, child then parent.
  8. Observe that three entities are created without issue.
  9. Update composer.json to lock DI package to 4.2.3
  10. Create single parent parent entity
  11. Observe ORMInvalidArgumentException::newEntitiesFoundThroughRelationships error.

Notes: Using step debugging I was able to set a breakpoint in the constructor of the service, and record the spl_object_id() of the EM being injected. And I set a second breakpoint in the controller, and recorded a second, different ID for the EM.

I have committed my test project for this at https://github.com/kingchills/di-test

Hello, I currently have this issue with symfony/dependency-injection v3.4.21 but not in v3.4.18. (I can’t test v3.4.19 and v3.4.20)

I have a service BeneficiaryManager which depends on EntityManagerInterface that I inject in services.yaml.

I investigated and I found a condition that has been added in the build container since v3.4.21:

// in var/cache/test/ContainerUcir7yw/srcTestDebugProjectContainer.php
// ...
    /**
     * Gets the private 'App\Client\Manager\BeneficiaryManager' shared autowired service.
     *
     * @return \App\Client\Manager\BeneficiaryManager
     */
    protected function getBeneficiaryManagerService()
    {
        $a = ${($_ = isset($this->services['doctrine.orm.client_entity_manager']) ? $this->services['doctrine.orm.client_entity_manager'] : $this->getDoctrine_Orm_ClientEntityManagerService()) && false ?: '_'};

        if (isset($this->services['App\\Client\\Manager\\BeneficiaryManager'])) {
            return $this->services['App\\Client\\Manager\\BeneficiaryManager'];
        }

        return $this->services['App\\Client\\Manager\\BeneficiaryManager'] = new \App\Client\Manager\BeneficiaryManager($a, ${($_ = isset($this->services['property_accessor']) ? $this->services['property_accessor'] : $this->getPropertyAccessorService()) && false ?: '_'});
    }
// ...

Here the v3.4.18 version (it works):

    /**
     * Gets the private 'App\Client\Manager\BeneficiaryManager' shared autowired service.
     *
     * @return \App\Client\Manager\BeneficiaryManager
     */
    protected function getBeneficiaryManagerService()
    {
        return $this->services['App\Client\Manager\BeneficiaryManager'] = new \App\Client\Manager\BeneficiaryManager(${($_ = isset($this->services['doctrine.orm.client_entity_manager']) ? $this->services['doctrine.orm.client_entity_manager'] : $this->getDoctrine_Orm_ClientEntityManagerService()) && false ?: '_'}, ${($_ = isset($this->services['property_accessor']) ? $this->services['property_accessor'] : $this->getPropertyAccessorService()) && false ?: '_'});
    }

This function is called more than once in the 2 versions. I think (in v3.4.21): when it’s called the second time, it recreates the entitymanager then overrides $this->services['doctrine.orm.client_entity_manager'] (done in the function $this->getDoctrine_Orm_ClientEntityManagerService()) and as we returns the App\\Client\\Manager\\BeneficiaryManager because it already exists (the new condition), it keeps the old entitymanager and has not injected the second.

In the v3.4.18, it recreates and overrides all times the App\\Client\\Manager\\BeneficiaryManager and has always the last instance of entitymanager.

Excuse me if I’m not clear, it’s difficult to explain. šŸ˜‰ I hope this can helps the fix.

Hello there, bug seems fixed with symfony 3.4.31! šŸ‘ Thank you!

Fix will be part of 4.3.4 / 3.4.31

Issue still occurs in my reproducer with 4.2.10 and 4.3.2.

I should have a failing test case shortly…

Minimal repro I could get if someone wants to look at it : https://github.com/Lctrs/symfony-issue-30091.

Because it’s not 😃 One of the edges in the loop is targetting a setter, which makes the loop breakable.

Locking at 4.2.2 solves this issue for me

I would like to provide you with additionnal feedback:

I have been able to reproduce this bug several times on my project. From what I have seen, this may occur when the EntityManager is injected both in a Doctrine EventSubscriber and in other ā€œregularā€ services.

Adding symfony/proxy-manager-bridge in my project solved this for me, as stated here by @dmaicher

Found a fix that works well (at least in my applications).

In every service, that needs an EntityManager flush to database, I inject the doctrine RegistryInterface instead of the EntityManagerInterface and then get the manager from the registry. Which is basically the same that we do in controllers all the time.

use Symfony\Bridge\Doctrine\RegistryInterface;

/**
 * @var RegistryInterface
 */
protected $doctrine;

public function __construct(RegistryInterface $doctrine)
{
    $this->doctrine = $doctrine;
}

public function generate()
{
    $entity = new Entity()
    $em = $this->doctrine->getManager();
    $em->persist($entity);
    $em->flush();
}

I have also found that locking symfony/dependency-injection at 4.2.2 solves the issue.

I have reproducer provided here and here, but they all involve complex applications and I didn’t manage to reduce the case to a minimal service graph. All involve the Doctrine Configuration service… I need to find some time to fully concentrate on the topic to understand what’s going on. It’s non trivial 😃

Note that this is not a BC break, it’s just a bug 😃