如何从服务中访问应用程序参数?

95

在我的控制器中,我使用以下代码访问应用程序参数(即位于/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%

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


这个问题看起来很像,但我的问题实际上回答了它(控制器中的参数),我说的是从服务中访问。


可能是如何在Symfony2控制器中从parameters.yml读取?的重复问题。 - Tomas Votruba
我的问题实际上回答了这个问题(来自控制器的参数),我在这里谈论的是从服务中访问。 - Pierre de LESPINAY
我不确定我理解你的意思。你同意复制吗? 现在Symfony中控制器是服务。 - Tomas Votruba
我不同意这个重复问题。另一个问题明确要求控制器,可以轻松使用“$this->getParameter()”获取参数。 - Pierre de LESPINAY
没错,我同意。而且这仍然是可能的。现在也有一种趋势,即远离容器注入到任何地方,并转向构造函数注入。由于PSR-4服务自动发现和参数绑定:https://symfony.com/blog/new-in-symfony-3-4-local-service-binding,它更加清晰简洁,使用起来更短。 - Tomas Votruba
10个回答

133

您可以通过在服务定义中指定它们来向您的服务传递参数,就像注入其他服务一样。例如,在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。 - Yassine CHABLI
my_param1 的值从哪里来? - Sliq
1
@Sliq,你可以在parameters.yml文件中定义它。 - Graph

54

清洁的方式 2018

自2018年和Symfony 3.4以来,有了更加清洁的方法-易于设置和使用。

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

如何在2个步骤中设置它?

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"
    }
}

即时升级可用!

如果您使用旧的方法,可以使用Rector自动化进行操作。

阅读更多

这被称作构造函数注入优先于服务定位器方法。

要了解更多信息,请查看我的文章如何以清晰的方式在Symfony控制器中获取参数。(已经测试并针对新的Symfony主版本(5、6...)进行更新)。


1
我会选择其他代码示例而不是控制器类,因为OP想要在任何服务中注入参数,并且SF3控制器默认启用了自动装配。 - alpadev
感谢您的评论。上面的配置适用于任何服务、控制器、存储库或自己的服务。没有区别。 - Tomas Votruba
请注意,这种方法为该配置中定义的所有服务创建可注入值。 - PhoneixS

24

自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.


3
请注意,这会注入所有参数,适用于需要大量参数的服务。 - PhoneixS

21

不要逐个映射所需的参数,为什么不允许您的服务直接访问容器呢?这样做,如果添加了与您的服务相关的新参数,则无需更新映射。

要做到这一点:

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

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作为“arguments”添加。
services:
  my_service_id:
    class: ...\MyServiceClass
    arguments: ["@service_container"]  // <- Add this

55
将整个容器传递下去会破坏依赖注入的目的。你的类应该只被给予它实际运行所需的东西,而不是整个容器。 - richsage
@richsage,有没有其他方法可以实现类似的结果 - 这样服务声明就不需要为每个参数更新?这看起来比逐个注入参数要整洁一些。 - Batandwa
1
将整个容器传递给服务是一个非常糟糕的想法。正如@richsage所说,它不符合依赖注入的目的。如果您不想使用依赖注入,则不要使用Symfony2 :) - tersakyan
2
@tersakyan,那控制器呢?默认情况下所有控制器都可以访问控制器。那我们不应该也使用控制器吗? :) - Alex Zheka
@AlexZheka "所有控制器都可以访问控制器"我不明白你的意思。 - tersakyan
他可能是指默认情况下,所有控制器都可以通过get()方法访问容器。然而,我认为不扩展实现ContainerAwareInterface的基本Symfony控制器是一个好的方法。基本的Symfony2控制器类只是添加了一些常见的辅助方法,可以避免使用。 - tomazahlin

7

在Symfony 4.1中,解决方案非常简单。

以下是原始帖子的片段:

// 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

本文讲述了Symfony 4.1中的新功能,使您可以将容器参数作为服务轻松获取。从现在开始,在您的应用程序中,您可以通过自动依赖注入来轻松地访问这些参数,而无需手动访问全局$container对象。


请问您能否给我建议,如何将它传递给单元测试? - Varg

6

为了解决提出的一些问题,我定义了一个数组参数并注入它。稍后添加新参数只需要将其添加到参数数组中,而不需要改变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;
    // ...
}

在撰写本评论时,不幸的是,在Symfony中无法嵌套参数,请参阅文档:https://symfony.com/doc/current/service_container/parameters.html#getting-and-setting-container-parameters-in-php - Tomas Votruba

2
截至2022年和Symfony 6.1版本,您可以使用#[Autowire]属性直接从服务的构造函数中访问参数(请参见此博客文章)。
以下是Symfony博客文章中提供的示例:
use Symfony\Component\DependencyInjection\Attribute\Autowire;

class MyService
{
    public function __construct(
        #[Autowire(service: 'some_service')]
        private $service1,

        #[Autowire(expression: 'service("App\\Mail\\MailerConfiguration").getMailerMethod()')]
        private $service2,

        #[Autowire('%env(json:file:resolve:AUTH_FILE)%')]
        private $parameter1,

        #[Autowire('%kernel.project_dir%/config/dir')]
        private $parameter2,
    ) {}

    // ...
}

1

@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;
}

完成。


你能进一步解释一下吗?其他解决方案到底哪里出了问题 - 是否有任何错误信息提示? - Nico Haase
他在构造函数中使用了ParameterBagInterface $params,但为了充分利用services.yaml中的参数配置,我使用了依赖注入。 - Dung
你能进一步解释一下吗?richsage的答案中并没有包含ParameterBagInterface,而是一个要注入的参数列表,就像你的代码一样。 - Nico Haase
我的答案是在2012年发布的,那时候 Symfony 生态系统只有2版本。我现在不再使用 Symfony,因此未更新至后续版本。 - richsage

-2

这里是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()
{
    //...
}

我认为这是更好的方式。


-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'

1
提供的代码没有正确使用 DI - 注入整个容器被认为是不好的风格,因为你隐藏了真正的依赖关系。 - Nico Haase
我认为你误解了概念,在我的示例中,我只展示了一个通用情况。如有疑问,请在投票之前查询官方symfony文档。https://symfony.com/doc/current/components/dependency_injection.html - shades3002
你能进一步解释吗?链接的文档清楚地说明注入容器不是一个好主意,并且没有展示任何使用这种类型注入的例子 - 因为显然,当你注入整个容器时,你并没有注入依赖项。 - Nico Haase
1
请使用 ParameterBagInterface - Mooncake

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接