为什么自动装配为事件订阅者提供了不同的实体管理器实例? [英] Why does autowiring provides a different entity manager instance for event subscribers?

查看:50
本文介绍了为什么自动装配为事件订阅者提供了不同的实体管理器实例?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在symfony4项目中提供了不同的服务,这为实体管理器注入了资源.我发现,当他们调用self :: $ container-> get('doctrine')-> getManager()时,它们所使用的学说事件订阅者和服务将获得与其他服务不同的实体管理器实例.我在项目中看到了多达三个不同的实例,但是我不知道在哪种情况下会创建更多的实例.

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.

我已将功能spl_object_id添加到所有构造函数中,以查看对象使用哪个实体管理器实例.以下代码具有两个服务和一个事件订阅者.事件订阅者使用第一个服务.我希望所有这些都使用相同的实体管理器实例,因为服务容器的总体思路是,某种类型的对象只能创建一次.但是显然创建了两个实体管理器实例,一个实例用于事件订阅者及其使用的所有服务,另一个实例用于所有其他实体.

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:

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

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:

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:

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.yaml:

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

phpunit测试运行的结果:

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)

我希望实体管理器的对象ID在所有位置都相同,即只有一个对象.这表明有两个实例.在Symfony 2.8中运行此操作仅会导致一个实例.

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.

问题:

  • 为什么容器/自动装配会创建两个或更多不同的实体管理器实例,例如什么时候使用原则事件订阅者?
  • 如何防止这种情况?

重要吗:我使用php 7.2.5,symfony 4.3.1和dormrine orm 2.6.3.

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

我刚刚发现,不仅实体管理器有多个实例,而且还有一些我自己的服务.我还不知道为什么.测试中的问题是,我先初始化测试中的某些服务,然后再将其用于其他服务.如果初始化的服务是不同的实例,则使用它们的服务将失败.

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.

推荐答案

我认为,此行为与phpunit和/或KernelTestCase/WebTestCase某种程度上相关,但仅部分原因.

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());
    }
}

并且我在服务类中添加了用于实体管理器ID的吸气剂:

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);
    }
}

这将导致以下输出:

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.

我还发现,还有 IS 与测试有关:

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

  1. 除了Symfony 2.8之外,KernerTestCase和WebTestCase都具有earnDown方法,该方法在EACH测试用例之后调用.用这种方法重置内核.这意味着,在测试中,您不能将服务存储在静态变量中,而不能在所有测试用例中使用它,因为内核及其服务对象在每个测试用例之间都会发生变化.
  2. 在使用WebTestCase进行的测试中,问题甚至更严重.当调用bootKernel和createClient(再次引导内核!)时,将重新创建所有对象,并使用不同的实体管理器.对此有一个错误报告.这是一个示例:
  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']);
    }
}

这将导致以下输出:

 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!

这篇关于为什么自动装配为事件订阅者提供了不同的实体管理器实例?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆