web-dev-qa-db-ja.com

Symfony 4:コンテナでパブリックサービスをオーバーライドする

プロジェクトを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);

テストの場合のみ、この問題をどのように修正できますか?

ありがとうございました

9
Mohammed Mehira

最後に、私は解決策を見つけました。多分最高ではないかもしれませんが、それはうまくいきます:

別のテストコンテナークラスを作成し、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;
    }

それは本当に醜いですが、それは働いています。

1
Mohammed Mehira

置換はSymfony 3.3から廃止されました。サービスを置き換える代わりに、エイリアスを使用してみてください。 http://symfony.com/doc/current/service_container/alias_private.html

また、このアプローチを試すことができます:

$this->container->getDefinition('user.user_service')->setSynthetic(true);を実行する前に$container->set()

php 7.2のテストでsymfonyサービスを置き換える

2
kallosz

私はこのようなテストをいくつか持っています(実際のコードはいくつかのアクションを実行して結果を返します。test-versionはすべての回答に対してfalseを返します)。

環境ごとにカスタム構成を作成して使用し(例:services_test.yaml、またはSymfony4の場合はおそらくtest/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'スタイルでも同様に機能します。

0
Alister Bulman