【问题标题】:How to access an application parameters from a service?如何从服务访问应用程序参数?
【发布时间】:2012-06-27 21:36:46
【问题描述】:

从我的控制器中,我访问应用程序参数(/app/config 中的参数)

$this->container->getParameter('my_param')

但我不知道如何从服务访问它(我想我的服务类不应该扩展 Symfony\Bundle\FrameworkBundle\Controller\Controller)。

我是否应该像这样将所需的参数映射到我的服务注册中:

#src/Me/MyBundle/Service/my_service/service.yml
parameters:
    my_param1: %my_param1%
    my_param2: %my_param2%
    my_param3: %my_param3%

或类似的东西?我应该如何从服务访问我的应用程序参数?


This question 看起来是一样的,但实际上我的答案是(来自控制器的参数),我说的是从服务访问。

【问题讨论】:

  • 我的问题实际上回答了这个问题(来自控制器的参数),我说的是从这里的服务访问
  • 我不确定我是否理解你。你同意重复吗?现在,控制器是 Symfony 中的服务。
  • 我不同意这个副本。另一个问题是专门要求使用$this->getParameter() 轻松获取参数的控制器。
  • 这是真的,我同意。这仍然是可能的。还有一种趋势是远离容器在任何地方注入并转向构造函数注入。多亏了 PSR-4 服务自动发现和参数绑定:symfony.com/blog/new-in-symfony-3-4-local-service-binding,它干净且使用起来更短。

标签: php symfony yaml


【解决方案1】:

2018 年清洁方式

自 2018 年和 Symfony 3.4 以来有更简洁的方式 - 易于设置和使用。

您可以通过类的构造函数将参数传递给类,而不是使用容器和服务/参数定位器反模式。别担心,这不是耗时的工作,而是设置一次后忘记的方法。

如何分两步设置?

1。 config.yml

# config.yml
parameters:
    api_pass: 'secret_password'
    api_user: 'my_name'

services:
    _defaults:
        autowire: true
        bind:
            $apiPass: '%api_pass%'
            $apiUser: '%api_user%'

    App\:
        resource: ..

2。任何Controller

<?php declare(strict_types=1);

final class ApiController extends SymfonyController
{
    /**
     * @var string 
     */
    private $apiPass;

    /**
     * @var string
     */
    private $apiUser;

    public function __construct(string $apiPass, string $apiUser)
    {
        $this->apiPass = $apiPass;
        $this->apiUser = $apiUser;
    }

    public function registerAction(): void
    {
        var_dump($this->apiPass); // "secret_password"
        var_dump($this->apiUser); // "my_name"
    }
}

即时升级就绪!

如果您使用较旧的方法,您可以automate it with Rector。

了解更多

这称为构造函数注入服务定位器方法。

要了解更多信息,请查看我的帖子How to Get Parameter in Symfony Controller the Clean Way。

(它已经过测试,我会为新的 Symfony 主要版本(5、6...)进行更新。

【讨论】:

  • 我会采用控制器类以外的其他东西作为代码示例,因为 OP 希望在任何服务中注入参数,并且默认情况下在 SF3 控制器中启用自动装配
  • 感谢您的评论。上面的配置适用于任何服务、控制器、存储库或自己的服务。没有区别。
【解决方案2】:

@richsage 是正确的(对于 Symfony 3.?),但它不适用于我的 Symfony 4.x。所以这里是 Symfony 4。

在 services.yaml 文件中

parameters:
    param1: 'hello'

Services:
    App\Service\routineCheck:
            arguments:
                $toBechecked: '%param1%'  # argument must match in class constructor

在你的服务类routineCheck.php文件中做这样的构造函数

private $toBechecked;

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

public function echoSomething()
{
    echo $this->toBechecked;
}

完成。

【讨论】:

  • 你能进一步解释一下吗?其他解决方案究竟有什么问题 - 是否给出了任何错误消息?
  • 他在构造函数中使用了 ParameterBagInterface $params,但为了充分利用 services.yaml 中的参数配置,我使用了依赖注入。
  • 你能进一步解释一下吗? Richsage 的答案不包含那个 ParameterBagInterface,而是一个要注入的参数列表,就像你的代码一样
  • 我的答案是在 2012 年发布的,当时生态系统只有 Symfony 2。我不再使用 Symfony,因此没有更新后续版本。
【解决方案3】:

在symfony 4中,我们可以通过依赖注入的方式访问参数:

服务:

   use Symfony\Component\DependencyInjection\ContainerInterface as Container;

   MyServices {

         protected $container;
         protected $path;

         public function __construct(Container $container)
         {
             $this->container = $container;
             $this->path = $this->container->getParameter('upload_directory');
         }
    }

parameters.yml:

parameters:
     upload_directory: '%kernel.project_dir%/public/uploads'

【讨论】:

  • 提供的代码没有正确使用 DI - 注入整个容器被认为是不好的风格,因为你隐藏了真正的依赖关系
  • 我认为您误解了这些概念,在示例中我只展示了一个一般情况。如果有疑问,请在投票前查阅官方的 symfony 文档。 symfony.com/doc/current/components/dependency_injection.html
  • 您能进一步解释一下吗?链接文档清楚地指出注入容器不是一个好主意,并且没有显示任何使用这种类型注入的示例 - 很明显,当您注入整个容器时,您并没有注入依赖项
  • 改用ParameterBagInterface。
【解决方案4】:

使用 Symfony 4.1 解决方案非常简单。

这是来自原始帖子的 sn-p:

// src/Service/MessageGenerator.php
// ...

use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;

class MessageGenerator
{
    private $params;

    public function __construct(ParameterBagInterface $params)
    {
        $this->params = $params;
    }

    public function someMethod()
    {
        $parameterValue = $this->params->get('parameter_name');
        // ...
    }
}

原帖链接: https://symfony.com/blog/new-in-symfony-4-1-getting-container-parameters-as-a-service

【讨论】:

    【解决方案5】:

    自 symfony 4.1 以来有一种非常干净的新方法来实现它

    <?php
    // src/Service/MessageGeneratorService.php
    
    use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
    
    class MessageGeneratorService
    {
     private $params;
     public function __construct(ParameterBagInterface $params)
     {
          $this->params = $params;
     }
     public function someMethod()
     {
         $parameterValue = $this->params->get('parameter_name');
    ...
     }
    }
    

    来源:https://symfony.com/blog/new-in-symfony-4-1-getting-container-parameters-as-a-service.

    【讨论】:

      【解决方案6】:

      这里是 Symfony 3.4。

      经过一些研究,我认为通过其构造函数将参数传递给类/服务始终不是一个好主意。 想象一下,如果您需要向控制器/服务传递比 2 或 3 个更多的参数。然后呢?最多传递 10 个参数会很荒谬。

      相反,在 yml 中声明服务时,使用 ParameterBag 类作为依赖项,然后使用任意数量的参数。

      一个具体的例子,假设你有一个邮件服务,比如 PHPMailer,并且你想在 paramters.yml 文件中拥有 PHPMailer 连接参数:

      #parameters.yml
      parameters:
          mail_admin: abc@abc.abc
          mail_host: mail.abc.com
          mail_username: noreply@abc.com
          mail_password: pass
          mail_from: contact@abc.com
          mail_from_name: contact@abc.com
          mail_smtp_secure: 'ssl'
          mail_port: 465
      
      #services.yml
      services:
          app.php_mailer:
              class: AppBundle\Services\PHPMailerService
              arguments: ['@assetic.parameter_bag'] #here one could have other services to be injected
              public: true
      
      # AppBundle\Services\PHPMailerService.php
      ...
      use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
      ...
      class PHPMailerService
      {
          private $parameterBag;
          private $mailAdmin;
          private $mailHost;
          private $mailUsername;
          private $mailPassword;
          private $mailFrom;
          private $mailFromName;
          private $mailSMTPSecure;
          private $mailPort;
      }
      public function __construct(ParameterBag $parameterBag)
      {
          $this->parameterBag = $parameterBag;
      
          $this->mailAdmin      = $this->parameterBag->get('mail_admin');
          $this->mailHost       = $this->parameterBag->get('mail_host');
          $this->mailUsername   = $this->parameterBag->get('mail_username');
          $this->mailPassword   = $this->parameterBag->get('mail_password');
          $this->mailFrom       = $this->parameterBag->get('mail_from');
          $this->mailFromName   = $this->parameterBag->get('mail_from_name');
          $this->mailSMTPSecure = $this->parameterBag->get('mail_smtp_secure');
          $this->mailPort       = $this->parameterBag->get('mail_port');
      }
      public function sendEmail()
      {
          //...
      }
      

      我认为这是一个更好的方法。

      【讨论】:

        【解决方案7】:

        您可以像注入其他服务一样将参数传递给您的服务,方法是在服务定义中指定参数。例如,在 YAML 中:

        services:
            my_service:
                class:  My\Bundle\Service\MyService
                arguments: [%my_param1%, %my_param2%]
        

        其中%my_param1% 等对应于名为my_param1 的参数。那么你的服务类构造函数可以是:

        public function __construct($myParam1, $myParam2)
        {
            // ...
        }
        

        【讨论】:

        • 在参数不存在的情况下有没有办法处理?而不是 symfony 异常 IOC 。
        • 而my_param1的值从何而来?
        • @Sliq,你在 parameters.yml 中定义
        【解决方案8】:

        作为解决提到的一些问题,我定义了一个数组参数然后注入它。稍后添加新参数只需要添加到参数数组,而不需要对 service_container 参数或构造进行任何更改。

        所以扩展 @richsage 答案:

        parameters.yml

        parameters:
            array_param_name:
                param_name_1:   "value"
                param_name_2:   "value"
        

        services.yml

        services:
            my_service:
                class:  My\Bundle\Service\MyService
                arguments: [%array_param_name%]
        

        然后在类里面访问

        public function __construct($params)
        {
            $this->param1 = array_key_exists('param_name_1',$params)
                ? $params['param_name_1'] : null;
            // ...
        }
        

        【讨论】:

        【解决方案9】:

        与其一一映射你需要的参数,为什么不让你的服务直接访问容器呢?这样做,如果添加了新参数(与您的服务相关),您不必更新映射。

        这样做:

        对您的服务类进行以下更改

        use Symfony\Component\DependencyInjection\ContainerInterface; // <- Add this
        
        class MyServiceClass
        {
            private $container; // <- Add this
            public function __construct(ContainerInterface $container) // <- Add this
            {
                $this->container = $container;
            }
            public function doSomething()
            {
                $this->container->getParameter('param_name_1'); // <- Access your param
            }
        }
        

        在你的 services.yml 中添加 @service_container 作为“参数”

        services:
          my_service_id:
            class: ...\MyServiceClass
            arguments: ["@service_container"]  // <- Add this
        

        【讨论】:

        • -1。完整地传递容器违背了依赖注入的目的。你的类应该只被赋予它实际需要操作的东西,而不是整个容器。
        • @richsage,是否有替代方法可以实现类似的结果 - 因此不会为每个参数更新服务声明?这看起来也比一个一个地注入参数要简洁一些。
        • 将整个容器传递给服务是一个非常糟糕的主意。正如@richsage 所说,它不符合依赖注入的目的。如果你不想使用依赖注入,那就不要使用 Symfony2 :)
        • @tersakyan,那么控制器呢?默认情况下,所有控制器都可以访问控制器。那么我们也不应该使用控制器吗? :)
        • @AlexZheka "所有控制器都可以访问控制器" 我不明白你的意思。
        猜你喜欢
        • 2016-12-25
        • 2012-10-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-18
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多