【问题标题】:Symfony2 access private services in testsSymfony2 在测试中访问私有服务
【发布时间】:2015-02-20 15:29:24
【问题描述】:

目前我正在测试 Symfony2 中的一些服务,我正在尝试使用 Guzzle MockPlugin 来控制 CURL 响应。使用 Symfony 2.3.8 版本。我遇到了一个有趣的行为,我不确定这是否是 Symfony2 错误。

我在 services.yml 中有这些服务:

lookup_service_client:
    class: FOO
    public: false
    factory_service: lookup_client_builder
    factory_method: build

lookup_repository_auth_type:
    class: AuthType
    arguments: ["@lookup_service_client"]

lookup_repository_cancel_reason:
    class: CancelReason
    arguments: ["@lookup_service_client"]

payment_service_client:
    class: FOO
    public: false
    factory_service: payment_client_builder
    factory_method: build

payment_repository:
    class: Payment
    arguments: ["@payment_service_client"]

类的名称并不重要。可以看到“lookup_service_client”和“lookup_service_client”都是PRIVATE服务。

我有一个测试类,它扩展了 Symfony\Bundle\FrameworkBundle\Test\WebTestCase。在一项测试中,我需要执行以下操作:

$lookup = $this->client->getContainer()->get('lookup_service_client');

$payment = $this->client->getContainer()->get('payment_service_client');

我预计,将这些服务设置为 PRIVATE,不会让我在测试中从容器中检索服务,但实际结果是:

$lookup = $this->client->getContainer()->get('lookup_service_client'); => returns the service instance

$payment = $this->client->getContainer()->get('payment_service_client'); => returns an exception saying: "You have requested a non-existent service"

这两个 service_client 服务之间的唯一区别是“lookup_service_client”被注入到其他几个服务中,而“payment_service_client”只被注入到一个其他服务中。

所以,问题是:

  1. 为什么我可以从容器“lookup_service_client”中检索,因为我已将其设置为私有?

  2. 为什么我可以检索“lookup_service_client”,但无法检索“payment_service_client”,因为上面给出了唯一的区别?

  3. 是我可以访问私有服务的 Symfony2 错误吗?

【问题讨论】:

    标签: php symfony dependency-injection phpunit private


    【解决方案1】:

    在 Symfony 4.1 中有一些新的变化:

    在 Symfony 4.1 中,我们做了同样的事情,现在测试默认允许获取私有服务。

    实际上,基于 WebTestCase 和 KernelTestCase 的测试现在通过 $client->getContainer() 或允许获取未删除的私有服务的 static::$container 属性访问特殊容器。

    您可以在news post 中了解更多信息。

    虽然这不是错误,但绝对是违反直觉的。 manual 明确表示:

    现在服务是私有的,你不应该获取服务 直接从容器中:

    $container->get('foo');

    这可能有效也可能无效,具体取决于容器的优化方式 服务实例,即使在它工作的情况下,也是 已弃用。简单地说:如果你这样做,可以将服务标记为私有 不想直接从您的代码中访问它。

    这就是核心团队决定在 Symfony 4 中make this behavior more consistent and intuitive 的原因:

    使用 Container::set() 方法设置或取消设置私有服务在 Symfony 3.2 中已弃用,在 4.0 中不再支持;

    使用 Container::has() 检查私有服务的存在在 Symfony 4.0 中总是返回 false;

    在 Symfony 3.2 中不推荐使用 Container::get() 方法请求私有服务,并且在 4.0 中不再返回该服务。

    【讨论】:

      【解决方案2】:

      2018+ 和 Symfony 3.4/4.0+ 解决方案

      这种方法with all its pros/cons is described in this post with code examples


      访问私有服务的最佳解决方案是添加编译器通行证,将所有服务公开以供测试。而已。实际效果如何?

      1。更新内核

       use Symfony\Component\HttpKernel\Kernel;
      +use Symplify\PackageBuilder\DependencyInjection\CompilerPass\PublicForTestsCompilerPass;
      
       final class AppKernel extends Kernel
       {
           protected function build(ContainerBuilder $containerBuilder): void
           {
               $containerBuilder->addCompilerPass('...');
      +        $containerBuilder->addCompilerPass(new PublicForTestsCompilerPass());
           }
       }
      

      2。要求或创建自己的编译器通行证

      PublicForTestsCompilerPass 的样子:

      use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
      use Symfony\Component\DependencyInjection\ContainerBuilder;
      
      final class PublicForTestsCompilerPass implements CompilerPassInterface
      {
          public function process(ContainerBuilder $containerBuilder): void
          {
              if (! $this->isPHPUnit()) {
                  return;
              }
      
              foreach ($containerBuilder->getDefinitions() as $definition) {
                  $definition->setPublic(true);
              }
      
              foreach ($containerBuilder->getAliases() as $definition) {
                  $definition->setPublic(true);
              }
          }
      
          private function isPHPUnit(): bool
          {
              // defined by PHPUnit
              return defined('PHPUNIT_COMPOSER_INSTALL') || defined('__PHPUNIT_PHAR__');
          }
      }
      

      要使用这个类,只需通过以下方式添加包:

      composer require symplify/package-builder
      

      当然,更好的方法是使用满足您需求的自己的类(您可能会使用 Behat 进行测试等)。

      那么您的所有测试将继续按预期工作!

      告诉我,这对你有什么作用。

      【讨论】:

        【解决方案3】:

        在容器中检查它们:

        container:debug lookup_service_client
        
        container:debug payment_service_client
        

        在您的示例中,它们都有“FOO”类,也许就是这样

        【讨论】:

        • 正如我上面所说,类的名称并不重要。它实际上是“ServiceClient”而不是“FOO”。
        • 您提到的命令的结果是:1)container:debug lookup_service_client [container] 服务lookup_service_client 服务ID 的信息lookup_service_client 类ServiceClient 标签- 范围容器公共无合成无必需文件- 2)容器:调试payment_service_client [Symfony\Component\DependencyInjection\Exception\InvalidArgumentException] 服务定义“payment_service_client”不存在。
        • 在 Symfony 编译它的容器时,payment_service_client 似乎被删除了,但是为什么它只删除了这两个服务之一呢?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-03-07
        • 1970-01-01
        • 1970-01-01
        • 2023-03-13
        • 2014-02-09
        • 2012-07-13
        相关资源
        最近更新 更多