Symfony 4:覆盖容器中的公共服务

i5desfxk  于 2023-04-12  发布在  其他
关注(0)|答案(6)|浏览(155)

我正在将我们的项目迁移到Symfony 4。在我的测试套件中,我们使用PHPUnit进行功能测试(我的意思是,我们调用端点并检查结果)。通常,我们模拟服务来检查不同的步骤。
自从我迁移到Symfony 4后,我就遇到了这个问题:Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: The "my.service" service is already initialized, you cannot replace it.当我们这样重新定义它时:static::$container->set("my.service", $mock);
仅用于测试,如何解决此问题?
谢谢你

gmxoilav

gmxoilav1#

自Symfony 3.3起,不建议使用替换。您应该尝试使用别名代替替换服务。http://symfony.com/doc/current/service_container/alias_private.html
你也可以试试这个方法:
执行$container->set()之前的$this->container->getDefinition('user.user_service')->setSynthetic(true);
在php 7.2的测试中替换Symfony服务

yiytaume

yiytaume2#

最后,我找到了一个解决方案。也许不是最好的,但是,它起作用了:
我创建了另一个测试容器类,并使用Reflection覆盖了services属性:

<?php

namespace My\Bundle\Test;

use Symfony\Bundle\FrameworkBundle\Test\TestContainer as BaseTestContainer;

class TestContainer extends BaseTestContainer
{
    private $publicContainer;

    public function set($id, $service)
    {
        $r = new \ReflectionObject($this->publicContainer);
        $p = $r->getProperty('services');
        $p->setAccessible(true);

        $services = $p->getValue($this->publicContainer);

        $services[$id] = $service;

        $p->setValue($this->publicContainer, $services);
    }

    public function setPublicContainer($container)
    {
        $this->publicContainer = $container;
    }

Kernel.php:

<?php

namespace App;

use Symfony\Component\HttpKernel\Kernel as BaseKernel;

class Kernel extends BaseKernel
{
    use MicroKernelTrait;

    public function getOriginalContainer()
    {
        if(!$this->container) {
            parent::boot();
        }

        /** @var Container $container */
        return $this->container;
    }

    public function getContainer()
    {
        if ($this->environment == 'prod') {
            return parent::getContainer();
        }

        /** @var Container $container */
        $container = $this->getOriginalContainer();

        $testContainer = $container->get('my.test.service_container');

        $testContainer->setPublicContainer($container);

        return $testContainer;
    }

很难看,但很有效。

sshcrbum

sshcrbum3#

我有几个这样的测试(真实的的代码执行一些操作并返回结果,测试版本只是对每个答案返回false)。
如果您为每个环境创建并使用自定义配置(例如:一个services_test.yaml,或者在Symfony 4中可能是tests/services.yaml),并首先让它包含dev/services.yaml,但随后覆盖所需的服务,将使用最后一个定义。
app/config/services_test.yml:

imports:
    - { resource: services.yml }

App\BotDetector\BotDetectable: '@App\BotDetector\BotDetectorNeverBot'

# in the top-level 'live/prod' config this would be 
# App\BotDetector\BotDetectable: '@App\BotDetector\BotDetector'

在这里,我使用一个接口作为一个服务名称,但它也会做同样的'@service.name'样式。

evrscar2

evrscar24#

正如我所理解的,这意味着类X已经被注入(由于其他一些依赖关系)您的代码试图用self::$container->set(X:class, $someMock)覆盖它之前。

4urapxun

4urapxun5#

对这个问题的所有答案似乎都忽略了Symfony更换服务的完整历史:
1.在Symfony 3.2之前,即使在初始化后也可以替换服务。

  1. Symfony 3.2 deprecated能够用$service->set()替换服务,即使是出于测试目的。
    1.在一些社区反馈中,DX在测试中被弃用,用$service->set()替换服务的能力是restored in 3.3.10,但有一个限制,即被替换的服务必须没有被“初始化”。换句话说,服务必须尚未注入到其他服务中(因为容器很难知道如何重新初始化所有服务的整个图,如果这样的服务被替换的话)。
    因此,如果您收到The "my.service" service is already initialized, you cannot replace it.消息,这意味着以下两件事之一:
    1.您试图替换的服务已用于初始化另一个服务。您可能需要在测试的早期替换该服务,或者重新排列测试中初始化服务的顺序。
  2. (未在任何地方记录)您正在尝试替换lazy服务。惰性服务在定义后立即由容器注入一个惰性加载存根,这会导致容器将其视为已初始化。作为一种解决方法,在services_test.yaml文件中将lazy服务定义为非惰性。我通过单步执行具有惰性服务的项目中的代码发现了这一点,这是我不得不使用的变通方法。
xmjla07d

xmjla07d6#

如果您使用Symfony 3.4及以下版本,您可以在容器中进行服务,无论它是私有的还是公共的。只会发出deprication通知,内容类似于问题的错误消息。
在Symfony 4.0上,发现了问题的错误。
但是在Symfony 4.1及更高版本上,你可以依靠特殊的“测试”容器。要学习如何使用它,请考虑以下链接:

相关问题