@egormmm
Борітеся — поборете!

Как в symfony создать сервис, у которого зависимость определяется окружением?

Хотел бы при выполнении юнит тестов (env=test) из сервис контейнера получить тестируемый сервис, который был "приготовлен" с "фейковой" зависимостью. Какой конкретный класс в качестве зависимости использовать должно определяться в зависимости от env.

Например:
MyService 
{
  private $dependency;

  public function __constructor(DependencyInterface $dependency)
  {
    $this->dependency = $dependency;
  }

   public function getDependency(): DependencyInterface
   {
      return $this->dependency;
   }
}

У DependencyInterface усть 2 конкретных класса:

RealDependency implement DependencyInterface{}
FakeDependency implement DependencyInterface{}


В тесте хотелось бы получить FakeDependency, а в других окружениях - RealDependency:
MyServiceTest extends KernelTestCase
{
  public function testAction()
  {
    $kernel = self::bootKernel();
    $service = $kernel->getContainer()->get('app.my_service');

    $this->assertInstanceOf(FakeDependency::class, $service->getDependency());
   }
}


Есть ли такая возможность?
  • Вопрос задан
  • 146 просмотров
Решения вопроса 2
BoShurik
@BoShurik Куратор тега Symfony
Symfony developer
Если зависимость от APP_ENV, то проще так
# config/services.yaml
services:
    DependencyInterface: '@RealDependency'

# config/services_test.yaml
services:
    DependencyInterface: '@FakeDependency'

Если нужна зависимость от какой-то другой переменной окружения, то надо использовать вариант Максим Федоров , т.к. контейнер компилируемый и на этапе компиляция значение из переменных окружения неизвестны
Ответ написан
Maksclub
@Maksclub
maksfedorov.ru
Вариант самый простой и удобный от BoShurik — регистрация сервисов для конфигов под нужное окружение

Если у вас это все в бандле происходит, то:
ВАРИАНТ — ФАБРИКА

Использовать фабрику и с ее помощью регистрировать ваш сервис.
Фабрика уже сама создаст сервис с нужной зависимостью. Можете в фабрике сами зависимости для вашего сервиса занести например в приватную переменную или регистрировать как-то более хитро — просто их регать через Service Method Calls — мой рекомендуемый вариант.

Итого — рекомендуемый и готовый код (неймспейсы поменять):
// Класс, который содержит ваши сервисы под разные окружения
class MyServiceDependencyRegistry
{
    private $dependencies = [];
    
    public function register(DependencyInterface $dependency, string $env)
    {
        // Тут всякие проверки можно сделать
        // (особенно, что константа допустима)
        $this->dependencies[$env] = $dependency;
    }
    
    public function getDependency(string $env): DependencyInterface
    {
        // Тут проверочки
        // особенно, что константа допустима и есть с такой константой сервис
        // иначе исключение
        return $this->dependencies[$env];
    }
}

class MyServiceFactory
{
    private $dependencies;

    public function __construct(MyServiceDependencyRegistry $dependencies)
    {
        $this->dependencies = $dependencies;
    }

    public function createNewService(string $env)
    {
        $envDependency = $this->dependencies->getDependency($env);

        // Если у вашего сервиса больше зависимостей
        // то вам нужно их передать в фабрику и тут уже передать в ваш сервис
        return new MyService($envDependency);
    }
}



# Регаем ваши зависимости (если сервисы  — они должны быть зареганы) 
# Помним, что сами значения ключей (окружения) лучше константой использовать
App\Email\MyServiceDependencyRegistry:
    calls:
         - [register, ['@App\Email\RealDependency', 'prod']]
         - [register, ['@App\Email\FakeDependency', 'test']]

# Регаем фабрику, которая принимает в себя сервис, 
# который отдаст вам  зависимости по ключу
App\Email\MyServiceFactory:
   arguments: 
      - '@App\Email\MyServiceDependencyRegistry'

# Регаем ваш сервис, указав фабрику и ее метод,
# который в зависимости от переменной создаст нужный инстанс
App\Email\MyService:
   factory:   ['@App\Email\MyServiceFactory', createNewService]
   arguments: 
      - '%env(APP_ENV)%'
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
skobkin
@skobkin
Гентушник, разработчик на PHP и Symfony.
Можно использовать expressions: https://symfony.com/doc/current/service_container/...

А можно подсмотреть как у Symfony сервисы подменяются в зависимости от того какая среда (EventDispatcher, контейнер, etc).
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы