Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

Thank you

like image 575
Mohammed Mehira Avatar asked Jul 25 '18 13:07

Mohammed Mehira


3 Answers

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.

like image 92
Mohammed Mehira Avatar answered Nov 08 '22 01:11

Mohammed Mehira


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

like image 8
kallosz Avatar answered Nov 08 '22 00:11

kallosz


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.

like image 1
Alister Bulman Avatar answered Nov 08 '22 00:11

Alister Bulman