Search code examples
phpsymfonysymfony4

Symfony 4 : Override public services in container


I am migrating our project to Symfony 4. In my test suites, we used PHPUnit for functional tests (I mean, we call endpoints and we check result). Often, we mock services to check different steps.

Since I migrated to Symfony 4, I am facing this issue: Symfony\Component\DependencyInjection\Exception\InvalidArgumentException: The "my.service" service is already initialized, you cannot replace it. when we redefine it like this : static::$container->set("my.service", $mock);

Only for tests, how can I fix this issue?


Solution

  • Finally, I found a solution. Maybe not the best, but, it's working:

    I created another test container class and I override the services property using 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;
        }
    

    It's really ugly, but it's working.