I have a Sumfony 4.3 command that processes some data and loops through a number of "processors" to do the processing. The code uses a factory (autowired) which then instantiates the command.
use App\Entity\ImportedFile;
use App\Service\Processor\Processor;
class Factory implements FactoryInterface
{
/** #var array */
private $processors;
/** #var TestClausesInterface */
private $testClauses;
private $em;
private $dataSetProvider;
private $ndviFromNasaService;
private $archivalHashService;
private $mailer;
private $projectDir;
public function __construct(
TestClausesInterface $testClauses,
ValidProcessorList $processors,
EntityManagerInterface $em,
DataSetProvider $dataSetProvider,
NDVIFromNasaService $ndviFromNasaService,
ArchivalHashService $archivalHashService,
\Swift_Mailer $mailer,
$projectDir)
{
$this->processors = $processors;
$this->testClauses = $testClauses;
$this->em = $em;
$this->dataSetProvider = $dataSetProvider;
$this->ndviFromNasaService = $ndviFromNasaService;
$this->archivalHashService = $archivalHashService;
$this->mailer = $mailer;
$this->projectDir = $projectDir;
}
public function findProcessorForFile(ImportedFile $file)
{
...
if ($found){
$candidates = $this->recursive_scan( $this->projectDir.'/src/Processor');
foreach ($candidates as $candidate){
if (substr($candidate,0,strlen('Helper')) === 'Helper'){
continue;
}
try {
$candidate = str_replace($this->projectDir.'/src/Processor/', '', $candidate);
$candidate = str_replace('/','\\', $candidate);
$testClassName = '\\App\\Processor\\'.substr( $candidate, 0, -4 );
/* #var Processor $test */
if (!strstr($candidate, 'Helper')) {
$test = new $testClassName($this->testClauses, $this->em, $this->dataSetProvider, $this->ndviFromNasaService, $this->archivalHashService, $this->mailer, $this->projectDir);
}
However I still have to:
autowire all arguments both in the Factory and Processor top class
pass all arguments in correct order to the Processor
I have around 70 subclasses of Processor. All of them use EntityInterface, but only a couple use SwiftMailer and the other dependencies.
As I am adding services to be used only by a few Processors, I am looking for a way to autowire these arguments only at the Processor level. Ideally, also without adding service definitions to services.yml
In summary, I would like to be able to add a dependency to any subclass of Processor, even if it is a parent class of other subclasses and have the dependency automatically injected.
There is much it is not immediately obvious in your code, but the typical way to resolve this is by using a "service locator". Docs.
Let's imagine you have several services implementing the interface Processor:
The interface:
interface Processor {
public function process($file): void;
}
Couple implementation:
class Foo implements Processor
{
public function __construct(DataSetProvider $dataSet, ArchivalHashService $archivalHash, \Swift_Mailer $swift) {
// initialize properties
}
public function process($file) {
// process implementation
}
public static function getDefaultIndexName(): string
{
return 'candidateFileOne';
}
}
Couple implementations:
class Bar implements Processor
{
public function __construct(\Swift_Mailer $swift, EntityManagerInterface $em) {
// initialize properties
}
public function process($file) {
// process implementation
}
public static function getDefaultIndexName(): string
{
return 'candidateFileTwo';
}
}
Note that each of the processors have completely different dependencies, and can be auto-wired directly, and that each of them has a getDefaultIndexName() method.
Now we'll "tag" all services implementing the Processor interface:
# services.yaml
services:
# somewhere below the _defaults and the part where you make all classes in `src` available as services
_instanceof:
App\Processor:
tags:
- { name: "processor_services", default_index_method: 'getDefaultIndexName' }
Attention here: The documentation says that if you define a public static function getDefaultIndexName() it will be picked by default. But I've found this not to be working at the moment. But if you define the default_index_method you can wire it to a method of your choice. I'm keeping the getDefaultIndexName for the time being, but you can pick something of your own choice.
Now, if you need this processes in a console command, for example:
use Symfony\Component\DependencyInjection\ServiceLocator;
class MyConsoleCommand
{
private ServiceLocator $locator;
public function __construct(ServiceLocator $locator)
{
$this->locator = $locator;
}
}
To inject the service locator you would do:
#services.yaml
services:
App\HandlerCollection:
arguments: [!tagged_locator { tag: 'processor_services' } ]
And to fetch any of the processors from the service locator you would do:
$fooProcessor = $this->locator->get('candidateFileOne');
$barProcessor = $this->locator->get('candidateFileTwo');
Summping up, basically what you need is:
Define a shared interface for the processors
Use that interface to tag all the processor services
Define a getDefaultIndexName() for each processor, which helps you match files to processors.
Inject a tagged service locator in the class that need to consume this services
And you can leave all services auto-wired.
Note: You could use an abstract class instead of an interface, and it would work the same way. I prefer using an interface, but that's up to you.
For completion sake, here is a repo with the above working for Symfony 4.3.
Related
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.
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)
{
// ...
}
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...)).
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.
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
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
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;
// ...
}
#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.
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.
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'
I have a simple class which looks like this:
<?php
namespace App\Algorithm;
use App\Dao\MatchDao;
use App\Service\MatchService;
class Calculator {
private $users;
private $matchDao;
function __construct(MatchService $matchService, MatchDao $matchDao) {
$this->users = $matchService->users;
$this->matchDao = $matchDao;
}
public function hourlyRate() {
$query = $this->matchDao->getSingleColumn('Payment', 'hourly_rate', 32);
var_dump($query);
}
}
But I get the following error message:
Circular reference detected for service "App\Algorithm\Calculator",
path: "App\Algorithm\Calculator -> App\Service\MatchService ->
App\Algorithm\Calculator".
MatchService.php
<?php
namespace App\Service;
use App\Algorithm\Calculator;
use App\Algorithm\Collection;
class MatchService {
public $users;
private $collection;
private $calculator;
function __construct(Collection $collection, Calculator $calculator) {
$this->collection = $collection;
$this->calculator = $calculator;
}
public function getMatch($data) {
$this->users = $this->collection->getAllUsers($data);
$this->calculator->hourlyRate();
return 1;
}
}
The problem would be MatchService but what exactly am I doing wrong?
As several people have pointed out, the circular dependency comes from that fact that you are trying to inject the Calculator into MatchService and at the same time, injecting MatchService into the Calculator. No way to create one before creating the other.
Looking a bit more deeply, it appears that Calculator is using the MatchService to get list of users. As a second problem, Calculator is trying to get the users before MatchService has generated them.
Here is one possible refactoring:
class Calculator
{
private $matchDao;
public function __construct(MatchDao $matchDao)
{
$this->matchDao = $matchDao;
}
public function getHourlyRate($users) // Added argument
{
$query = $this->matchDao->getSingleColumn('Payment', 'hourly_rate', 32);
}
}
class MatchService
{
private $collection;
private $calculator;
public function __construct(Collection $collection, Calculator $calculator)
{
$this->calculator = $calculator;
$this->collection = $collection;
}
public function getMatch($data)
{
$users = $this->collection->getAllUsers($data);
$this->calculator->getHourlyRate($users);
}
}
Removing MatchService from the Calculator's constructor solves the circular dependency problem. Passing $users to getHourlyRate solves the problem of trying to get users before they are available.
This is course is just one possible solution. It's not clear from your posted code if Calculator really needs $users or not.
This usually occurs when classes are dependency injecting each other, hence the circular reference.
Given you above example, your class MatchService injects Collection and Calculator. One of these (would assume calculator as collection is probably a doctrine class) dependency injects your MatchService.
Here is how I imagine your classes are supt:
class MatchService
{
public $users;
private $collection;
private $calculator;
public function __construct(Collection $collection, Calculator $calculator) {
$this->collection = $collection;
$this->calculator = $calculator;
}
}
class Calculator
{
private $matchService;
public function __construct(MatchService $matchService)
{
$this->matchService = $matchService;
}
}
You have a couple of options:
More services with fewer dependencies
Using statics
It's hard for us to solve for you as it's dependent on how you architect your application.
It is kind of obvious that you are injecting service A into Service B, and, also, Service B into Service A .
Seems kind of not logical to do so, but sometimes is needed.
In my case, I have two services :
_MySesion -> Which prototypes Symfony Session
_MyClient -> Responsible for identify the client and get its DB Credentials
I use the MySession to store those credentials, as so, it will be available to the whole system, but, to get those credentials using MyClient, I need some info stored into MySession .... See, two services that need each other to work ...
I start to see this same
Circular reference detected for service
just after upgrade to Symfony 5. And, sfy5 itself suggested the solution :
composer require symfony/proxy-manager-bridge
Remember that the services may be set with
lazy : true
More info on Symfony Docs
I have some classes that require dependencies injected into their constructors. This allows me to inject mocks (e.g. from prophecy) for testing.
I'm interested in using a container to help configure and access these objects, and I've looked at Pimple for this (I also looked at PHP-DI although I couldn't get that to resolve stuff on a quick attempt).
All good so far. BUT, the problem I have is that the application (Drupal 7) is built around thousands of functions which do not belong to an object that can have dependencies injected into.
So I need these functions to be able to access the services from the container. Further more, for testing purposes, I need to replace the services with mocks and new mocks.
So the pattern is like:
<?php
/**
* Some controller class that uses an injected mailing service.
*/
class Supporter
{
protected $mailer;
public function __construct(MailingServiceInterface $mailer) {
$this->mailer = $mailer;
}
public function signUpForMalings($supporter_id) {
$email = $this->getSupporterEmail($supporter_id);
$this->mailer->signup($email);
}
}
Then peppered in various functions I'd use:
<?php
/**
* A form submit handler called by the platform app,
* with a signature I can't touch.
*/
function my_form_submit($values) {
global $container;
if ($values['subscribe']) {
$supporter = $container->get('supporter');
$supporter->signUpForMailings($values['supporter_id']);
}
}
Elsewhere I may need to access the mailer directly...
<?php
/**
* example function requires mailer service.
*/
function is_signed_up($email) {
global $container;
return $container->get('mailer')->isSignedUp($email);
}
And elsewhere a function that calls those functions...
<?php
/**
* example function that uses both the above functions
*/
function sign_em_up($email, $supporter_id) {
if (!is_signed_up($email)) {
my_form_submit(['supporter_id'=>$supporter_id);
return TRUE;
}
}
Let's acknowledge that these functions are a mess - that's a deliberate representation of the problem. But let's say I want to test the sign_em_up function:
<?php
public testSignUpNewPerson() {
$mock_mailer = createAMockMailer()
->thatWill()
->return(FALSE)
->whenFunctionCalled('isSignedUp', 'wilma#example.com');
// Somehow install the mock malier in the container.
$result = sign_em_up('wilma#example.com', 123);
$this->assertTrue($result);
}
// ... imagine other tests which also need to inject mocks.
While I recognise that this is using the container as a Service Locator in the various global functions, I think this is unavoidable given the nature of the platform. If there's a cleaner way, please let me know.
However my main question is:
There's a problem with injecting mocks, because the mocks need to change for various tests. Lets say I swap out the mailer service (in Pimple: $container->offsetUnset('mailer'); $container['mailer'] = $mock_mailer;), but if Pimple had already instantiated the supporter service, then that service will have the old, unmocked mailer object. Is this a limitation of the containter software, or the general container pattern, or am I Doing It Wrong, or is it just a mess because of the old-school function-centred application?
Here's what I've gone for, in absence of any other suggestions!
Container uses Pimple\Psr11\ServiceLocator
I'm using Pimple, so the container's factories may look like this
<?php
use Pimple\Container;
use Pimple\Psr11\ServiceLocator;
$container = new Container();
$container['mailer'] = function ($c) { return new SomeMailer(); }
$container['supporters'] = function ($c) {
// Create a service locator for the 'Supporters' class.
$services = new ServiceLocator($c, ['mailer']);
return new Supporter($services);
}
Then the Supporter class now instead of storing references to the objects extracted from the container when it was created, now fetches them from the ServiceLocator:
<?php
use \Pimple\Psr11\ServiceLocator;
/**
* Some controller class that uses an injected mailing service.
*/
class Supporter
{
protected $services;
public function __construct(ServiceLocator $services) {
$this->services = $services;
}
// This is a convenience function.
public function __get($prop) {
if ($prop == 'mailer') {
return $this->services->get('mailer');
}
throw new \InvalidArgumentException("Unknown property '$prop'");
}
public function signUpForMalings($supporter_id) {
$email = $this->getSupporterEmail($supporter_id);
$this->mailer->signup($email);
}
}
In the various CMS functions I just use global $container; $mailer = $container['mailer'];, but it means that that in tests I can now mock any service and know that all code that needs that service will now have my mocked service. e.g.
<?php
class SomeTest extends \PHPUnit\Framework\TestCase
{
function testSupporterGetsMailed() {
global $container;
$supporter = $container['supporter'];
// e.g. mock the mailer component
$container->offsetUnset('mailer');
$container['mailer'] = $this->getMockedMailer();
// Do something with supporter.
$supporter->doSomething();
// ...
}
}
Lately I'm giving a try to phpspec. It works great, but I have got a problem with testing command handlers. For example in PHPUnit I test it that way:
/**
* #test
*/
public function it_should_change_an_email()
{
$this->repository->add($this->employee);
$this->handler->changeEmail(
new ChangeEmailCommand(
$this->employee->username()->username(),
'new#email.com'
)
);
Asserts::assertEquals(new Email('new#email.com'), $this->employee->email());
}
and setup:
protected function setUp()
{
$this->repository = new InMemoryEmployeeRepository();
$this->createEmployee();
$this->handler = new EmployeeCommandHandler($this->repository);
}
The main point is that this test make assertions on the Employee object to check if CommandHandler is working good. But in phpspec I can't make assertion on different object than the specifying one, in this case I can only make assertion on my CommandHandler. So how I can test a command handler in phpspec?
EDIT
Maybe spies are the way to go:
class EmployeeCommandHandlerSpec extends ObjectBehavior
{
const USERNAME = 'johnny';
/** #var EmployeeRepository */
private $employeeRepository;
public function let(EmployeeRepository $employeeRepository)
{
$this->employeeRepository = $employeeRepository;
$this->beConstructedWith($employeeRepository);
}
public function it_changes_the_employee_email(Employee $employee)
{
$this->givenEmployeeExists($employee);
$this->changeEmail(
new ChangeEmailCommand(self::USERNAME, 'new#email.com')
);
$employee->changeEmail(new Email('new#email.com'))->shouldHaveBeenCalled();
}
private function givenEmployeeExists(Employee $employee)
{
$this->employeeRepository->employeeWithUsername(new EmployeeUsername(self::USERNAME))
->shouldBeCalled()
->willReturn($employee);
}
}
Employee class I've already speced. So, maybe, in command handler it'll be enough to just check if the method of the Employee has been called. What do you think about it? Am I going in good direction?
Messaging
Indeed, you shouldn't verify the state, but expect certain interactions between objects. That's what OOP is about afterall - messaging.
The way you've done it in PHPUnit is state verification. It forces you to expose the state as you need to provide a "getter", which is not always desired. What you're interested in is that Employee's email was updated:
$employee->updateEmail(new Email('new#email.com'))->shouldBeCalled();
The same can be achieved with spies if you prefer:
$employee->updateEmail(new Email('new#email.com'))->shouldHaveBeenCalled();
Command/Query Separation
We usually only need to state our expectations against methods that have side effects (command methods from Command/Query separation). We mock them.
Query methods do not need to be mocked, but stubbed. You don't really expect that EmployeeRepository::employeeWithUsername() should be called. Doing so we're making assumptions about implementation which in turn will make refactoring harder. All you need is stubbing it, so if a method is called it returns a result:
$employeeRepository->employeeWithUsername(new EmployeeUsername(self::USERNAME))
->willReturn($employee);
Full example
class EmployeeCommandHandlerSpec extends ObjectBehavior
{
const USERNAME = 'johnny';
public function let(EmployeeRepository $employeeRepository)
{
$this->beConstructedWith($employeeRepository);
}
public function it_changes_the_employee_email(
EmployeeRepository $employees, Employee $employee
) {
$this->givenEmployeeExists($employees, $employee);
$this->changeEmail(
new ChangeEmailCommand(self::USERNAME, 'new#email.com')
);
$employee->changeEmail(new Email('new#email.com'))->shouldHaveBeenCalled();
}
private function givenEmployeeExists(
EmployeeRepository $employees, Employee $employee
) {
$employees->employeeWithUsername(new EmployeeUsername(self::USERNAME))
->willReturn($employee);
}
}
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.
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)
{
// ...
}
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...)).
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.
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
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
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;
// ...
}
#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.
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.
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'