95

From my controllers, I access the application parameters (those in /app/config) with

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

But I don't know how to access it from a service (I imagine my service class is not supposed to extend Symfony\Bundle\FrameworkBundle\Controller\Controller).

Should I map needed parameters into my service registration like this:

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

or something similar? How should I access to my application parameters from a service?


This question seems like the same but mine actually answers to it (parameters from a controller), I'm talking about accessing from a service.

Pierre de LESPINAY
  • 44,700
  • 57
  • 210
  • 307
  • Possible duplicate of [How do I read from parameters.yml in a controller in symfony2?](https://stackoverflow.com/questions/13901256/how-do-i-read-from-parameters-yml-in-a-controller-in-symfony2) – Tomas Votruba Mar 03 '18 at 13:02
  • My question actually answers to this one (parameters from a controller), I'm talking about accessing from a service here – Pierre de LESPINAY Mar 03 '18 at 15:07
  • I'm not sure I understand you. Do you agree with duplicate? Controllers are services in Symfony nowadays. – Tomas Votruba Mar 03 '18 at 17:19
  • I don't agree with the duplicate. The other question is specifically asking for Controllers which is easily getting parameters with `$this->getParameter()`. – Pierre de LESPINAY Mar 05 '18 at 06:44
  • That is true, I agree. And it is still possible. There is also trend to stepping away from container being injected anywhere and moving to constructor injection. Thanks to PSR-4 service autodiscovery and parameters binding: https://symfony.com/blog/new-in-symfony-3-4-local-service-binding, it's clean and much shorter to work with. – Tomas Votruba Mar 05 '18 at 10:04

10 Answers10

133

You can pass parameters to your service in the same way as you inject other services, by specifying them in your service definition. For example, in YAML:

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

where the %my_param1% etc corresponds to a parameter named my_param1. Then your service class constructor could then be:

public function __construct($myParam1, $myParam2)
{
    // ...
}
cn007b
  • 16,596
  • 7
  • 59
  • 74
richsage
  • 26,912
  • 8
  • 58
  • 65
55

The Clean Way 2018

Since 2018 and Symfony 3.4 there is much cleaner way - easy to setup and use.

Instead of using container and service/parameter locator anti-pattern, you can pass parameters to class via it's constructor. Don't worry, it's not time-demanding work, but rather setup once & forget approach.

How to set it up in 2 steps?

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

Instant Upgrade Ready!

In case you use older approach, you can automate it with Rector.

Read More

This is called constructor injection over services locator approach.

To read more about this, check my post How to Get Parameter in Symfony Controller the Clean Way.

(It's tested and I keep it updated for new Symfony major version (5, 6...)).

Tomas Votruba
  • 23,240
  • 9
  • 79
  • 115
  • 1
    I would have taken something else than a controller class as a code example as OP wants to inject parameters in any service and autowiring is enabled by default in SF3 controllers – alpadev Aug 21 '18 at 07:57
  • Thanks for your comment. The config above works for any service, controller, repository or own service. There is no difference. – Tomas Votruba Sep 10 '18 at 08:50
  • Have in mind that this approach creates the injectable value for all the services defined in that configuration. – PhoneixS Mar 04 '22 at 09:00
23

There is a very clean new way to achieve it since 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');
...
 }
}

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

Ousmane
  • 2,673
  • 3
  • 30
  • 37
  • 3
    Have in mind that this injects all the params and it is better suited for services that need a lot of params. – PhoneixS Mar 04 '22 at 08:58
21

Instead of mapping your needed parameters one by one, why not allowing your service to access the container directly? Doing so, you do not have to update your mapping if there is new parameters added (which relate to your service).

To do so:

Make following changes to your service class

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

Add @service_container as "arguments" in your services.yml

services:
  my_service_id:
    class: ...\MyServiceClass
    arguments: ["@service_container"]  // <- Add this
Ham L.
  • 465
  • 3
  • 4
  • 55
    -1. Passing the container around in its entirety defeats the purpose of dependency injection. Your class should only be given what it actually needs to operate, not the whole container. – richsage Nov 27 '15 at 14:00
  • @richsage, is there an alternative to achieve similar results - so the service declaration is not updated for each parameter? This also looks a bit neater than injecting parameters one-by-one. – Batandwa Dec 22 '15 at 11:56
  • 1
    Passing whole container to a service is a really bad idea. As @richsage says, it not fits dependency injection purpose. If you dont want to use dependency injection, then dont use Symfony2 :) – tersakyan Jan 21 '16 at 12:23
  • 2
    @tersakyan , but what about controllers then ? by default all controllers have access to controller. Then should we not use controllers as well ? :) – Alex Zheka May 14 '16 at 15:01
  • @AlexZheka "all controllers have access to controller" i didn't understand what you mean. – tersakyan May 16 '16 at 22:58
  • He probably meant that by default all controllers have access to container, with the get() method. However I believe NOT extending the base Symfony controller which implements ContainerAwareInterface is the good approach. The base symfony2 controller class just adds some common helper methods and can be avoided. – tomazahlin Jun 07 '16 at 10:02
7

With Symfony 4.1 the solution is quite simple.

Here is a snippet from the original post:

// 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');
        // ...
    }
}

Link to the original post: https://symfony.com/blog/new-in-symfony-4-1-getting-container-parameters-as-a-service

Carol-Theodor Pelu
  • 906
  • 2
  • 10
  • 26
6

As solution to some of issues mentioned, I define an array parameter then inject it. Adding a new parameter later just requires addition to parameter array without any change to service_container arguments or construct.

So extending on @richsage answer:

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%]

Then access inside class

public function __construct($params)
{
    $this->param1 = array_key_exists('param_name_1',$params)
        ? $params['param_name_1'] : null;
    // ...
}
Dave
  • 69
  • 1
  • 2
  • At the time of writing this comment, unfortunately, nesting of params is not possible in Symfony, see docs: https://symfony.com/doc/current/service_container/parameters.html#getting-and-setting-container-parameters-in-php – Tomas Votruba Mar 03 '18 at 13:01
1

@richsage is correct (for Symfony 3.?) but it did not work for my Symfony 4.x. So here is for Symfony 4.

in services.yaml file

parameters:
    param1: 'hello'

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

in your service class routineCheck.php file do constructor like so

private $toBechecked;

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

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

Done.

Dung
  • 19,199
  • 9
  • 59
  • 54
  • Can you explain that further? What exactly did not work with the other solution - are there any error messages given? – Nico Haase Dec 29 '19 at 13:35
  • He used ParameterBagInterface $params in his constructor, but to fully utilize parameter configuration in services.yaml I used dependency injection. – Dung Dec 29 '19 at 17:11
  • Can you explain that further? The answer by richsage does not contain that ParameterBagInterface, but a list of parameters to be injected, just like your code – Nico Haase Dec 30 '19 at 13:29
  • My answer was posted in 2012, when the ecosystem was only Symfony 2. I don't use Symfony anymore so haven't updated for subsequent versions. – richsage Nov 14 '20 at 19:20
1

As of 2022 and Symfony 6.1, you can access parameters directly from a service's constructor using the #[Autowire] attribute (see this blog post).

Here is the example provided in Symfony's blog post:

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,
    ) {}

    // ...
}
-2

Symfony 3.4 here.

After some researches, I don't think passing parameters to a class/service via it's constructor, is always a good idea. Imagine if you need to pass to a controller/service some more parameters than 2 or 3. What then? Would be ridiculous to pass, let's say, up to 10 parameters.

Instead, use the ParameterBag class as a dependency, when declaring the service in yml, and then use as many parameters as you wish.

A concrete example, let's say you have a mailer service, like PHPMailer, and you want to have the PHPMailer connection parameters in the paramters.yml file:

#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()
{
    //...
}

I think this is a better way.

Dan Costinel
  • 1,716
  • 1
  • 15
  • 23
-3

In symfony 4, we can access the parameters by means of dependency injection:

Services:

   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'
shades3002
  • 879
  • 9
  • 11
  • 1
    The provided code does not use DI properly - injecting the whole container is considered bad style, as you hide the real dependencies – Nico Haase Dec 29 '19 at 13:36
  • I think you are mistaking the concepts, in the example I only show a general case. If in doubt, consult the official symfony documentation before casting a vote. https://symfony.com/doc/current/components/dependency_injection.html – shades3002 Dec 29 '19 at 22:33
  • Can you explain that further? The linked documentation clearly states that injecting the container is not a good idea, and does not show any example that uses this type of injection - as clearly, you are not injecting dependencies when you inject the whole container – Nico Haase Dec 30 '19 at 13:31
  • 1
    use `ParameterBagInterface` instead. – Mooncake Jan 26 '21 at 10:51