Symfony 4在不同环境中服务本地绑定

时间:2018-07-27 09:30:54

标签: symfony symfony4

我必须在不同的环境中将具有不同值的参数绑定在一起,并且遇到问题。

我正在尝试:

# config/services.yaml
services:
    _defaults:
        bind:
            $param: 'param for PROD'

# config/services_dev.yaml
services:
    _defaults:
        bind:
            $param: 'param for DEV'

# src/Controller/SomeController.php
class MyController extends AbstractController
{
    public function example($param)
    {
        echo $param;
    }
}

但是,这迫使我必须在 services.yaml services_dev.yaml 文件中都定义所有服务,否则它将无法正常工作。

我想为任何环境共享一个 services.yaml ,并且仅覆盖自定义服务/绑定等,不具有两个相同的文件,其中列出了用于更改一个绑定值的所有服务


真正的问题是我必须创建两个具有相同接口的http客户端(真实和虚拟),在生产中加载真实的一个,在开发中加载虚拟,Symfony 4-s自动装配允许我注入接口在控制器中,然后选择要在绑定中使用的客户端:

# config/services.yaml
services:
    _defaults:
        bind:
            'ClientInterface': '@real_client'
    # More services here...

# config/services_dev.yaml
services:
    _defaults:
        bind:
            'ClientInterface': '@dummy_client'
    # Here I don't want to have another copy of the services, 
    # but it does not work without them

# Controller
public function someMethod(ClientInterface $client)
{
    // ...
}

在Symfony 2中,我能够扩展services.yml,而在services_dev.yml中仅定义要覆盖/添加的特定值,但是在Symfony 4中, services_dev.yaml 无法使用以下服务: services.yaml ,我必须在两个不同的文件中保持相同的服务,这很痛苦。

有建议吗?

谢谢。


我将用一个真实的例子再次更新帖子:

services.yaml

# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.

# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration
parameters:
    locale: 'en'
    app.access_token: '%env(string:APP_ACCESS_TOKEN)%'
    app.aws_version: '%env(string:AWS_VERSION)%'
    app.aws_profile: '%env(string:AWS_PROFILE)%'
    app.aws_region: '%env(string:AWS_REGION)%'
    app.aws_queue_url_creation: '%env(string:AWS_QUEUE_URL_CAMPAIGN_CREATION)%'
    app.aws_queue_url_edition: '%env(string:AWS_QUEUE_URL_CAMPAIGN_EDITION)%'
    app.redis_host: '%env(string:REDIS_HOST)%'
    app.redis_port: '%env(string:REDIS_PORT)%'

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
        public: false       # Allows optimizing the container by removing unused services; this also means
                            # fetching services directly from the container via $container->get() won't work.
                            # The best practice is to be explicit about your dependencies anyway.
        bind:
            App\Service\MessageSenderServiceInterface: '@App\Service\MessageSenderSqsService'

    # makes classes in src/ available to be used as services
    # this creates a service per class whose id is the fully-qualified class name
    App\:
        resource: '../src/*'
        exclude: '../src/{DependencyInjection,Entity,Migrations,Tests,Kernel.php}'

    # controllers are imported separately to make sure services can be injected
    # as action arguments even if you don't extend any base controller class
    App\Controller\:
        resource: '../src/Controller'
        tags: ['controller.service_arguments']

    # add more service definitions when explicit configuration is needed
    # please note that last definitions always *replace* previous ones

    # Authenticators
    App\Security\ApiKeyAuthenticator:
        arguments:
            - "%app.access_token%"

    # Clients
    App\Client\AwsSqsClient:
        arguments:
            - "%app.aws_version%"
            - "%app.aws_profile%"
            - "%app.aws_region%"

    App\Client\RedisClient:
        arguments:
            - "%app.redis_host%"
            - "%app.redis_port%"

    # Services
    App\Service\MessageSenderSqsService:
        arguments:
            - '@App\Client\AwsSqsClient'
            - '@App\Client\RedisClient'
            - "%app.aws_queue_url_creation%"
            - "%app.aws_queue_url_edition%"

    App\Service\MessageSenderRedisService:
        arguments:
            - '@App\Client\RedisClient'

services_dev.yaml

imports:
    - { resource: services.yaml }

services:
    # default configuration for services in *this* file
    _defaults:
        autowire: true      # Automatically injects dependencies in your services.
        autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
        public: false       # Allows optimizing the container by removing unused services; this also means
                            # fetching services directly from the container via $container->get() won't work.
                            # The best practice is to be explicit about your dependencies anyway.
        bind:
            App\Service\MessageSenderServiceInterface: '@App\Service\MessageSenderRedisService'

Controller.php

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;

class TestController extends AbstractController
{
    /**
     * @Route("/api/dummy")
     */
    public function dummyEndpoint(MessageSenderServiceInterface $messageSender)
    {
        echo get_class($messageSender); exit;
    }
}

这两个环境(prod和dev)从控制器的回波是

App\Service\MessageSenderSqsService

但是如果我将整个节点“ services”从services.yaml复制到services_dev.yaml并仅更改绑定配置,它会正常工作,并说注入的类是:

App\Service\MessageSenderRedisService

我刚刚注意到,如果我不触摸“ _defaults”节点,它会按预期工作,那么只有当我想覆盖服务的“ _defaults”节点时,问题才会开始...

3 个答案:

答案 0 :(得分:1)

最后,问题仅在于覆盖“ _defaults”节点(为了使项目中具有不同的“绑定”配置,我正在触摸该节点)。

在不覆盖 _defaults 的情况下扩展services.yaml,一切正常。解决方案是对服务进行不同的配置(按环境绑定),并且仅在services.yaml中使用“ _defaults”。

如果我们在其他文件中覆盖“ _defaults”,则也必须重新定义所有服务。

感谢大家的帮助。

答案 1 :(得分:0)

您可以在parameters的{​​{1}}部分中定义参数,并在config.yml中覆盖此参数。

config_dev.yml

此参数可以在# config.yml imports: # ... parameters: parameter_1: value 1 parameter_2: value 2 # ... framework: # ... # config_dev.yml imports: # ... parameters: parameter_1: dev value 1 # ... framework: # ... 中用作:

service.yml

答案 2 :(得分:0)

您有一些选择:

1。请勿使用bind并为不同的环境编写不同的服务配置

# services.yaml
App\Controller:
  arguments:
    - "@client"

# services_dev.yaml
App\Controller:
  arguments:
    - "@dummy_client"

2。使用bind并在每个环境的services.yaml中创建服务别名:

# services.yaml
services:
  some.client:
    alias: "@client"

# services_dev.yaml    
services:
  some.client:
    alias: "@dummy_client"

3。只需为每个环境配置一项ClientInterface服务:

# services.yaml
App\ClientInterface:
  class: App\RealClient

# services_dev.yaml
App\ClientInterface:
  class: App\DummyClient

4。使用工厂来创建此客户端取决于环境(但这对我而言不是很好的做法)

# services.yaml
App\ClientInterface:
  factory: ["@App\ClientFactory", create]
  arguments:
    - '%kernel.environment%'

class ClientFactory 
{
    public function create(string $env): ClientInterface
    {
        if ($env === 'dev') {
            return new DummyClient();
        } else {
            return new Client();
        }
    }
}

5。在您的情况下,如果您有这么多服务,并且想在所有服务中注入相同的服务,则可以使用选项#3,也可以为所有服务创建一个接口并使用_instanceof

# services.yaml
_instanceof:
  App\SomeCommonInterface:
    calls:
      - method: setSomeService # interface's method
        arguments:
          - '@service'

# services_dev.yaml
_instanceof:
  App\SomeCommonInterface:
    calls:
      - method: setSomeService
        arguments:
          - '@dummy_service'