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 及更高版本上,您可以依靠特殊的“测试”容器。要了解如何使用它,请考虑以下链接:
我正在将我们的项目迁移到 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 及更高版本上,您可以依靠特殊的“测试”容器。要了解如何使用它,请考虑以下链接: