How to Access an Application Parameters from a Service

How to access an application parameters from a service?

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

Accessing application parameters directly from service

It's possible, but not easy.

  1. Get the manifest xml:
var fc = new FabricClient();         
var application = (await fc.QueryManager.GetApplicationListAsync(new Uri (Context.CodePackageActivationContext.ApplicationName))).Single();
var applicationManifest = await fc.ApplicationManager.GetApplicationManifestAsync(application.ApplicationTypeName,
application.ApplicationTypeVersion);

  1. Use that xml to deserialize an object based on the XSD schema. C:\Program Files\Microsoft SDKs\Service Fabric\schemas\ServiceFabricServiceModel.xsd (ApplicationManifestType)

(sorry about the formatting)

How to get parameters of a service inside symfony controllers?

You cannot directly get a nested parameter. you have to get the parent key and you will get all the content as an array

$nachoImageConfig = $this->getParameter('nacho_image');
$nachoImageUpload = $nachoImageConfig['upload'];

From the doc (http://symfony.com/doc/current/service_container/parameters.html):

The used . notation is just a Symfony convention to make parameters easier to read. Parameters are just flat key-value elements, they can't be organized into a nested array

That is why in most config files, the entries under 'parameters:' are fully qualified like this:

parameters:

...

nacho_image.upload: true
nacho_image.progress: false
nacho_image.key: secret_id

how to get configuration parameter into a command

Argument Binding

In Symfony 3.4 or later, the recommended approach is to use Autowiring and Argument Binding. Allowing for the declaration of the variable name to be "bound" as an argument for ALL services defined in the config file, without needing to explicitly specify each service the parameter is used in.

In the same configuration file that defines the command service and autowiring, add the bind option to the _defaults specification, along with the desired variable name for the parameter.

app/config/services.yml

parameters:
PARAMETER_NAME: 'test'

services:
_defaults:
bind:
$PARAMETER_NAME: '%PARAMETER_NAME%'
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

AppBundle\:
resource: '../../src/AppBundle/*'
exclude: '../../src/AppBundle/{DependencyInjection,Entity,Tests}'

# Additional Services Below Here

Afterwards, Symfony will automatically pass the parameter value to the bound variable name when it is specified as an argument for the service constructor.

src/AppBundle/Command/MyCommand.php

namespace AppBundle\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class MyCommand extends Command
{

private $PARAMETER_NAME;

public function __construct($PARAMETER_NAME)
{
$this->PARAMETER_NAME = $PARAMETER_NAME;
parent::__construct();
}

// ...

public function execute(InputInterface $input, OutputInterface $output)
{
dump($this->PARAMETER_NAME);
exit;
}

public static function getDefaultName()
{
return 'app:my_command';
}
}


Parameter Injection

Another approach, to avoid needing to override __construct, is to inject the parameter value using a method, by extending the service definition using calls.

app/config/services.yml

parameters:
PARAMETER_NAME: 'test'

services:
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

AppBundle\:
resource: '../../src/AppBundle/*'
exclude: '../../src/AppBundle/{DependencyInjection,Entity,Tests}'

AppBundle\Command\MyCommand:
calls:
- [setParameterName, ['%PARAMETER_NAME%']]

# Additional Services Below Here

src/AppBundle/Command/MyCommand.php

namespace AppBundle\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class MyCommand extends Command
{

private $parameterName;

// ...

public function execute(InputInterface $input, OutputInterface $output)
{
dump($this->parameterName);
exit;
}

public function setParameterName($value)
{
$this->parameterName = $value;
}
}

Alternatively, dependency injection can be used to inject the Container or ParameterBag into the command, allowing it to function similarly to a Controller.

Injecting the entire Container or ParameterBag is highly
discouraged.
Only inject the parameter(s) and service(s) that are needed
instead.

In either of the below examples, ensure autowire and autoconfigure are enabled.

app/config/services.yml

parameters:
PARAMETER_NAME: 'test'

services:
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.

AppBundle\:
resource: '../../src/AppBundle/*'
exclude: '../../src/AppBundle/{DependencyInjection,Entity,Tests}'

# Additional Services Below Here

ContainerAwareCommand

(depreciated as of Symfony 4.2 - removed in Syfmony 5.0+)

Using ContainerAwareCommand works similarly to Parameter Injection, but instead of calling setParameterName(). With autowiring and autoconfig enabled, Symfony will automatically inject the entire container using setContainer() implemented from ContainerAwareInterface.

src/AppBundle/Command/MyCommand.php

namespace AppBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class MyCommand extends ContainerAwareCommand
{

// ...

public function execute(InputInterface $input, OutputInterface $output)
{
dump($this->getContainer()->getParameter('PARAMETER_NAME'));
exit;
}
}

ParameterBag Injection

Requires Symfony 4.1+

To inject the ParameterBag with Dependency Injection when autowire is enabled, add ParameterBagInterface to the service constructor.

src/AppBundle/Command/MyCommand.php

namespace AppBundle\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;

class MyCommand extends Command
{

private $parameterBag;

public function __construct(ParameterBagInterface $parameterBag)
{
$this->parameterBag = $parameterBag;
parent::__construct();
}

// ...

public function execute(InputInterface $input, OutputInterface $output)
{
dump($this->parameterBag->get('PARAMETER_NAME'));
exit;
}

public static function getDefaultName()
{
return 'app:my_command';
}
}

Symfony2: How to inject ALL parameters in a service?

Note: I know that this solution is not BEST from design point of view, but it does the job, so please avoid down-voting.

You can inject \AppKernel object and then access all parameters like this:

config.yml:

my_service:
class: MyService\Class
arguments: [@kernel]

And inside MyService\Class:

public function __construct($kernel)
{
$this->parameter = $kernel->getContainer()->getParameter('some.key');
// or to get all:
$this->parameters = $kernel->getContainer()->getParameterBag()->all();
}

Accessing Symfony parameters from Service

  1. If you don't want to pass many parameters to constructor,

a) you can pass many parameters as array.

parameters:
my_params:
foo: bar
baz: 1

services:
app.my_service:
class: \yourservice\
arguments:
- '%my_params%'

b) you can use setters

services:
app.my_service:
class: \yourservice\
calls:
- [setParameterA, ['%first_param%']]
- [setParameterB, ['%second_param%']]
- ...

  1. For your case is better to create Guzzle Client from Factory with given params. You can define many various clients and inject the specific one into your service.

`

services:
app.client_factory:
class: AppBundle\Service\ClientFactory

app.some_client:
class: GuzzleHttp\Client
factory: 'app.client_factory:create'
arguments:
- []
-
base_uri: '%url%/%version%'
headers:
User-Agent: 'MyAgent 0.1'
Accept: 'application/json'
content-type: 'application/json'
- ~

app.my_service:
class: \yourservice\
arguments:
- '@app.some_client'

How do I read from parameters.yml in a controller in symfony2?

In Symfony 2.6 and older versions, to get a parameter in a controller - you should get the container first, and then - the needed parameter.

$this->container->getParameter('api_user');

This documentation chapter explains it.

While $this->get() method in a controller will load a service (doc)

In Symfony 2.7 and newer versions, to get a parameter in a controller you can use the following:

$this->getParameter('api_user');

Get config parameters from controller within a custom bundle

Building your own bundle is not straightforward.

From the bundle structure you shared with us, I can see that you already have the Dependency injection folder.

This folder should contains two file:

  • Configuration.php
  • YourBundleNameExtension.php

The parameter you want to inject in your controller should be in the configuration of your bundle so you have to complete the Configuration.php to add it. (I'm not a pro for that so I let you search by yourself)

Moreover, in order to acces your configuration in your bundle's code, you have to inject the configuration as a parameter with a a prefix for your bundle. You can find an example to see how to do it here : https://github.com/Orbitale/CmsBundle/blob/4.x/DependencyInjection/OrbitaleCmsExtension.php

Now in your Resources/services.yaml you can add you controller as a service:

services:
bundle_name.controller.your_controller:
class: Your\Controller\Namespace
arguments:
- '%bundle_key.your_parameter_name%'

Something like that should work but it's maybe not totally clear. So if you have more questions, I'll try to answer you.

Don't hesitate to check existing bundles as a source of inspiration.

---------------------- EDIT TO ANSWER EDIT 2 ----------------------

From what I see, your bundle configuration key is "admin-aside-menu"? Maybe it should be "admin_aside_menu" instead (to match convention). Anyway :

Yeah you almost there but there is missing something. You can't directly define parameters in config from your bundle. Instead, when you will use your bundle from your application you will have a file like this in /config/packages/admin-aside-menu.yaml :

admin-aside-menu:
items:
- icon: 'Home/Chair2'
title: 'Prueba'
- icon: 'Home/Deer'
title: 'Prueba Venado'

This is your bundle configuration for your current usage and this should match the format you define in your AdminBundle/DependencyInjection/Configuration.php file. (I can't help you with that because it's not something I often do).

You can now remove totally the file AdminBundle/Resources/config/custom.yaml because the configuration is in your application.

Then in your extension, you can get this configuration to inject it in your the application parameters with a prefix for your bundle. If I modify your code, it should be something like this :

// AdminBundle/DependencyInjection/ExampleVendorAdminExtension .php
class ExampleVendorAdminExtension extends Extension {

/**
* Loads a specific configuration.
*
* @throws \InvalidArgumentException When provided tag is not defined in this extension
*/
public function load(array $configs, ContainerBuilder $container) {
$loader = new YamlFileLoader( $container, new FileLocator(__DIR__.'/../Resources/config') );
// $loader->load('custom.yaml'); not needed anymore
$loader->load('services.yaml');

$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);

// This part inject your config in parameters
foreach ($config as $key => $value) {
$container->setParameter('admin-aside-menu.'.$key, $value);
}
}
}

Now your config parameters are set!

Last step, inject it in your controller :

// AdminBundle/Resources/config/services.yaml
services:
admin-bundle.controller.admin-controller:
class: ExampleVendor\AdminBundle\Controller\AdminController
arguments:
- "%admin-aside-menu.items%"

Is this ok ?



Related Topics



Leave a reply



Submit