首页 文章

Symfony 4为不同环境中的本地绑定提供服务

提问于
浏览
1

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

我在尝试这个:

# 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.yamlservices_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;
    }
}

控制器对envs(prod和dev)的回声是

App\Service\MessageSenderSqsService

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

App\Service\MessageSenderRedisService

我刚刚注意到,如果我没有触及“_defaults”节点,它会按预期工作,只有当我想覆盖服务的_defaults节点时才会出现问题...

3 回答

  • 1

    最后问题只是覆盖了“_defaults”节点(为了在项目中使用不同的“绑定”配置,我正在触摸它) .

    在没有覆盖 _defaults 的情况下扩展services.yaml,一切都按预期工作 . 解决方案是根据环境对服务进行不同的配置,并且只在services.yaml中使用"_defaults" .

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

    谢谢大家的帮助 .

  • 0

    您可以在 config.ymlparameters 部分中定义参数,并在 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 中:

    # service.yml
    services:
        _defaults:
            bind:
                $param: '%parameter_1%'
    
  • 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'
    

相关问题