How to access an application parameters from a service?

PhpSymfonyYaml

Php Problem Overview


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.

Php Solutions


Solution 1 - Php

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

Solution 2 - Php

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

Solution 3 - Php

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

Solution 4 - Php

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.

Solution 5 - Php

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

Solution 6 - Php

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

Solution 7 - Php

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

Solution 8 - Php

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.

Solution 9 - Php

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'

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPierre de LESPINAYView Question on Stackoverflow
Solution 1 - PhprichsageView Answer on Stackoverflow
Solution 2 - PhpTomas VotrubaView Answer on Stackoverflow
Solution 3 - PhpHam L.View Answer on Stackoverflow
Solution 4 - PhpOusmaneView Answer on Stackoverflow
Solution 5 - PhpDaveView Answer on Stackoverflow
Solution 6 - PhpCarol-Theodor PeluView Answer on Stackoverflow
Solution 7 - PhpDungView Answer on Stackoverflow
Solution 8 - PhpDan CostinelView Answer on Stackoverflow
Solution 9 - Phpshades3002View Answer on Stackoverflow