Symfony 4:覆盖容器中的 public 服务

Symfony 4 : Override public services in container

我正在将我们的项目迁移到 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);

仅用于测试,我该如何解决这个问题?

谢谢

我有几个这样的测试(真实代码执行一些操作并 returns 结果,测试版本只是 returns 每个答案都是错误的)。

如果您为每个环境创建和使用自定义配置(例如:services_test.yaml,或者在 Symfony4 中可能 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”样式执行相同的操作。

从 Symfony 3.3 开始不推荐使用替换。您应该尝试使用别名,而不是替换服务。 http://symfony.com/doc/current/service_container/alias_private.html

另外,你可以试试这个方法:

$this->container->getDefinition('user.user_service')->setSynthetic(true); 在做 $container->set()

之前

Replace Symfony service in tests for php 7.2

终于找到了解决办法。也许不是最好的,但是,它正在工作:

我创建了另一个测试容器 class 并使用反射覆盖了 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;
    }

它确实很丑陋,但它确实有效。

如果您使用 Symfony 3.4 及以下版本,您可以在容器中覆盖服务,无论它是私有的还是 public。只会发出deprication notice,内容类似提问的error message

在 Symfony 4.0 上,出现问题的错误。

但在 Symfony 4.1 及更高版本上,您可以依靠特殊的“测试”容器。要了解如何使用它,请考虑以下链接: