Symfony 4 : Override public services in container

10,395

Solution 1

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.

Solution 2

Replacing is deprecated since Symfony 3.3. Instead of replacing service you should try using aliases. http://symfony.com/doc/current/service_container/alias_private.html

Also, you can try this approach:

$this->container->getDefinition('user.user_service')->setSynthetic(true); before doing $container->set()

Replace Symfony service in tests for php 7.2

Solution 3

I've got a couple of tests like this (the real code performs some actions and returns a result, the test-version just returns false for every answer).

If you create and use a custom config for each environment (eg: a services_test.yaml, or in Symfony4 probably tests/services.yaml), and first have it include dev/services.yaml, but then override the service you want, the last definition will be used.

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'

Here, I'm using an Interface as a service-name, but it will do the same with '@service.name' style as well.

Share:
10,395

Related videos on Youtube

Mohammed Mehira
Author by

Mohammed Mehira

Updated on October 15, 2022

Comments

  • Mohammed Mehira
    Mohammed Mehira over 1 year

    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?

    Thank you

  • Mohammed Mehira
    Mohammed Mehira almost 6 years
    $this->container->getDefinition('user.user_service')->setSyn‌​thetic(true); I cannot use this in my tests suites. The compiler is already compiled.
  • kallosz
    kallosz almost 6 years
    if you want to replace service in your test code it means that you have something wrong design in your app.
  • Mohammed Mehira
    Mohammed Mehira over 5 years
    it's a functional test, it's a normal behaviour.
  • Frédéric Marchal
    Frédéric Marchal over 5 years
    @kallosz A service used by the app to send mails must not send mails during tests. It must be replaced by a mock. So must any service performing network communication, online payment or producing heavy load such as generating a pdf. Finally, a service must also be replaced by a mock to return controlled error codes to check the app is handling errors correctly. This is normal behavior during tests.