web-dev-qa-db-fra.com

Symfony 4: remplacer les services publics dans un conteneur

Je migre notre projet vers Symfony 4. Dans mes suites de tests, nous avons utilisé PHPUnit pour des tests fonctionnels (je veux dire, nous appelons des points de terminaison et nous vérifions le résultat). Souvent, nous nous moquons des services pour vérifier les différentes étapes.

Depuis que j'ai migré vers Symfony 4, je suis confronté à ce problème: Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: The "my.service" service is already initialized, you cannot replace it. Lorsque nous le redéfinissons comme ceci: static::$container->set("my.service", $mock);

Uniquement pour les tests, comment puis-je résoudre ce problème?

Je vous remercie

9
Mohammed Mehira

Enfin, j'ai trouvé une solution. Peut-être pas le meilleur, mais ça marche:

J'ai créé une autre classe de conteneur de test et je remplace la propriété services à l'aide de Reflection:

<?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;
    }

C'est vraiment moche, mais ça marche.

1
Mohammed Mehira

Le remplacement est obsolète depuis Symfony 3.3. Au lieu de remplacer le service, vous devriez essayer d'utiliser des alias. http://symfony.com/doc/current/service_container/alias_private.html

Vous pouvez également essayer cette approche:

$this->container->getDefinition('user.user_service')->setSynthetic(true); avant de faire $container->set()

Remplacer le service Symfony dans les tests pour php 7.2

2
kallosz

J'ai quelques tests comme celui-ci (le vrai code effectue certaines actions et renvoie un résultat, la version de test renvoie simplement false pour chaque réponse).

Si vous créez et utilisez une configuration personnalisée pour chaque environnement (par exemple: un services_test.yaml, ou dans Symfony4 probablement tests/services.yaml), et que vous incluez d'abord dev/services.yaml, mais remplacez ensuite le service souhaité, le la dernière définition sera utilisée.

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'

Ici, j'utilise une interface comme nom de service, mais cela fera de même avec le style '@ service.name'.

0
Alister Bulman