Why does autowiring provides a different entity manager instance for event subscribers?

早过忘川 提交于 2021-02-10 11:53:38

问题


I have different services in a symfony4 project, which get the entity manager injected. I found out, that doctrine event subscribers and services used by them get a different entity manager instance than the other services and when you call self::$container->get('doctrine')->getManager(). I have seen up to three different instances in my project, but I don't know under which circumstances even more instances are created.

I have added the function spl_object_id to all constructors to see which instance of the entity manager is used by the objects. The following code has two services and one event subscriber. The event subscriber uses the first service. I expected all of these to use the same entity manager instance, since the general idea of the service container is that objects of a certain type are only created once. But obviously two entity manager instances are created, one for the event subscriber and all services used by it and one for all others.

TestService1.php:

<?php

namespace App\Service;

use Doctrine\ORM\EntityManagerInterface;

class TestService1
{
    public function __construct(EntityManagerInterface $entityManager)
    {
        echo "\n Manager from TestService1:   ".spl_object_id($entityManager);
    }
}

TestService2.php

<?php

namespace App\Service;

use Doctrine\ORM\EntityManagerInterface;

class TestService2
{
    public function __construct(EntityManagerInterface $entityManager)
    {
        echo "\n Manager from TestService2:   ".spl_object_id($entityManager);
    }
}

TestSubscriber.php:

<?php

namespace App\Service;

use Doctrine\Common\EventSubscriber;
use Doctrine\ORM\EntityManagerInterface;

class TestSubscriber implements EventSubscriber
{
    public function __construct(EntityManagerInterface $entityManager, TestService1 $testService1)
    {
        echo "\n Manager from TestSubscriber: ".spl_object_id($entityManager);
    }

    public function getSubscribedEvents()
    {
    }
}

TestServiceTest.php:

<?php

namespace App\Tests\Service;

use App\Service\TestService1;
use App\Service\TestService2;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class TestServiceTest extends KernelTestCase
{
    public function testGetEntityManager()
    {
        self::bootKernel();

        $testObject1 = self::$container->get(TestService1::class);
        $testObject2 = self::$container->get(TestService2::class);

        echo "\n Manager from container:      ".spl_object_id(self::$container->get('doctrine')->getManager());
    }
}

services.yaml:

services:
   .....
    App\Service\TestSubscriber:
        tags:
            - { name: doctrine.event_subscriber}
    App\Service\TestService1:
        public: true
    App\Service\TestService2:
        public: true

Result of phpunit test run:

PHPUnit 6.5.14 by Sebastian Bergmann and contributors.

Testing App\Tests\Service\TestServiceTest
.                                                                   1 / 1 (100%)
 Manager from TestService1:   50
 Manager from TestSubscriber: 50
 Manager from TestService2:   386
 Manager from container:      386

Time: 200 ms, Memory: 16.00MB

OK (1 test, 1 assertion)

I would expect, that the object id of the entity manager is the same at all places, i.e. that there is only ONE object. This shows that there are two instances. Running this in Symfony 2.8 did result in only ONE instance.

Questions:

  • Why do container / autowiring create two or more different entity manager instances, e.g. when doctrine event subscribers are used?
  • How do I prevent this?

Should it be important: I use php 7.2.5, symfony 4.3.1 and doctrine orm 2.6.3.

Edit:

I just found out that not only the entity manager has multiple instances, but also some of my own services. I haven't found out yet why. The problem in tests is, that I initialise some services in tests before they are used by other services. When the initialised services are different instances, then the services using them fail.


回答1:


I thought, that this behaviour was related somehow to phpunit and/or the KernelTestCase/WebTestCase, but it is only in part.

I created a controller to use the objects:

<?php

namespace App\Controller;

use App\Service\TestService1;
use App\Service\TestService2;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class TestController extends AbstractController
{
    /**
     * @Route("/test_controller")
     */
    public function showAction(TestService1 $testService1, TestService2 $testService2)
    {
        return new Response('<br/>Testservice1 in controller: '.$testService1->getEmId().'<br/>Testservice2 in controller: '.$testService2->getEmId());
    }
}

and I added a getter for the entity managers id to the service classes:

<?php

namespace App\Service;

use Doctrine\ORM\EntityManagerInterface;

class TestService1
{
    /**
     * @var EntityManagerInterface
     */
    private $entityManager;

    public function __construct(EntityManagerInterface $entityManager)
    {
        echo "<br/>Manager from TestService1:   ".spl_object_id($entityManager);
        $this->entityManager = $entityManager;
    }

    public function getEmId()
    {
        return spl_object_id($this->entityManager);
    }
}

This results in the following output:

Manager from TestService1: 2897
Manager from TestSubscriber: 2897
Manager from TestService2: 3695
Testservice1 in controller: 2897
Testservice2 in controller: 3695

As you can see, there are here also TWO different entity manager objects, which makes passing doctrine objects between these services impossible, when they have to be changed or stored.

What I also found out, is that there IS also a relation to tests:

  1. Other than in Symfony 2.8, the KernerTestCase and the WebTestCase have a tearDown-method, which is called after EACH test case. In this method the kernel is reset. This means, that in tests you cannot store a service in a static variable and use it in all test cases, since the kernel and with that the service objects change between each test case.
  2. The problem can be even worse in tests using the WebTestCase. When calling bootKernel and createClient (which also boots the kernel, again!), all object are recreated, again with different entity managers. There is a bug report on this. Here is an example:
<?php

namespace App\Tests\Service;

use App\Entity\Status;
use App\Service\TestService1;
use App\Service\TestService2;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class TestServiceTest extends WebTestCase
{
    public function testGetEntityManager()
    {
        self::bootKernel();
        // I am unaware that createClient also boots the kernel, so let's store the EM object...
        $entityManager = self::$container->get('doctrine')->getManager();
        echo "\n Manager from container 1:    ".spl_object_id(self::$container->get('doctrine')->getManager());
        self::createClient();
        echo "\n Manager from container 2:    ".spl_object_id(self::$container->get('doctrine')->getManager());

        $testObject1 = self::$container->get(TestService1::class);
        $testObject2 = self::$container->get(TestService2::class);

        echo "\n Manager from container 3:    ".spl_object_id(self::$container->get('doctrine')->getManager());

        // This object is managed by one of the now 4!!! entity managers, passing it so a service using a different
        // EM will at best not work, but probable cause exceptions (object not under control of MY entity manager,
        // cannot persist) or even crash.
        $status = $entityManager->getRepository(Status::class)->findOneBy(['status' => 'member']);
    }
}

This results in the following output:

 Manager from TestService1:   60
 Manager from TestSubscriber: 60
 Manager from container 1:    399
 Manager from TestService1:   434
 Manager from TestSubscriber: 434
 Manager from container 2:    507
 Manager from TestService2:   507
 Manager from container 3:    507

We now have FOUR different entity manager instances!



来源:https://stackoverflow.com/questions/57361816/why-does-autowiring-provides-a-different-entity-manager-instance-for-event-subsc

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!