How can I inject ALL parameters in a service?
I know I can do: arguments: [%some.key%] which will pass the parameters: some.key: "value" to the service __construct.
My question is, how to inject everything that is under parameters in the service?
I need this in order to make a navigation manager service, where different menus / navigations / breadcrumbs are to be generated according to different settings through all of the configuration entries.
I know I could inject as many parameters as I want, but since it is going to use a number of them and is going to expand as time goes, I think its better to pass the whole thing right in the beginning.
Other approach might be if I could get the parameters inside the service as you can do in a controller $this -> container -> getParameter('some.key');, but I think this would be against the idea of Dependency Injection?
Thanks in advance!
It is not a good practice to inject the entire Container into a service. Also if you have many parameters that you need for your service it is not nice to inject all of them one by one to your service. Instead I use this method:
1) In config.yml I define the parameters that I need for my service like this:
parameters:
product.shoppingServiceParams:
parameter1: 'Some data'
parameter2: 'some data'
parameter3: 'some data'
parameter4: 'some data'
parameter5: 'some data'
parameter6: 'some data'
2) Then I inject this root parameter to my service like:
services:
product.shoppingService:
class: Saman\ProductBundle\Service\Shopping
arguments: [#translator.default, %product.shoppingServiceParams%]
3) In may service I can access these parameters like:
namespace Saman\ProductBundle\Service;
use Symfony\Bundle\FrameworkBundle\Translation\Translator;
class Shopping
{
protected $translator;
protected $parameters;
public function __construct(
Translator $translator,
$parameters
)
{
$this->translator = $translator;
$this->parameters = $parameters;
}
public function dummyFunction()
{
var_dump($this->getParameter('parameter2'));
}
private function getParameter($key, $default = null)
{
if (isset($this->parameters[$key])) {
return $this->parameters[$key];
}
return $default;
}
}
4) I can also set different values for different environments. For example in config_dev.yml
parameters:
product.shoppingServiceParams:
parameter1: 'Some data for dev'
parameter2: 'some data for dev'
parameter3: 'some data for dev'
parameter4: 'some data for dev'
parameter5: 'some data for dev'
parameter6: 'some data'
Another variant how to get parameters easy - you can just set ParameterBag to your service. You can do it in different ways - via arguments or via set methods. Let me show my example with set method.
So in services.yml you should add something like:
my_service:
class: MyService\Class
calls:
- [setParameterBag, ["#=service('kernel').getContainer().getParameterBag()"]]
and in class MyService\Class just add use:
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
and create 2 methods:
/**
* Set ParameterBag for repository
*
* #param ParameterBagInterface $params
*/
public function setParameterBag(ParameterBagInterface $params)
{
$this->parameterBag = $params;
}
/**
* Get parameter from ParameterBag
*
* #param string $name
* #return mixed
*/
public function getParameter($name)
{
return $this->parameterBag->get($name);
}
and now you can use in class:
$this->getParameter('your_parameter_name');
I believe you're supposed to pass the parameters individually. I think it's made that way by design so your service class is not dependent on the AppKernel. That way you can reuse your service class outside your Symfony project. Something that is useful when testing your service class.
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();
}
AppKernel would work but it's even worse (from a scope perspective) than injecting the container since the kernel has even more stuff in it.
You can look at xxxProjectContainer in your cache directory. Turns out that the assorted parameters are compiled directly into it as a big array. So you could inject the container and then just pull out the parameters. Violates the letter of the law but not the spirit of the law.
class MyService {
public function __construct($container) {
$this->parameters = $container->parameters; // Then discard container to preclude temptation
And just sort of messing around I found I could do this:
$container = new \arbiterDevDebugProjectContainer();
echo 'Parameter Count ' . count($container->parameters) . "\n";
So you could actually create a service that had basically a empty copy of the master container and inject it just to get the parameters. Have to take into account the dev/debug flags which might be a pain.
I suspect you could also do it with a compiler pass but have never tried.
Suggestion to define a service at services.yml, which will inject the parameterBag and allow access to any of your parameter
service.container_parameters:
public: false
class: stdClass
factory_service: service_container
factory_method: getParameterBag
Inject your service, and u can get your parameter using below
$parameterService->get('some.key');
As alternative approach would be that you can actually inject application parameters into your service via Container->getParameterBag in you bundle DI Extension
<?php
namespace Vendor\ProjectBundle\DependencyInjection;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\Config\FileLocator;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\DependencyInjection\Loader;
/**
* This is the class that loads and manages your bundle configuration
*
* To learn more see {#link http://symfony.com/doc/current/cookbook/bundles/extension.html}
*/
class VendorProjectExtension extends Extension {
/**
* {#inheritDoc}
*/
public function load(array $configs, ContainerBuilder $container) {
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__ . '/../Resources/config'));
$loader->load('services.yml');
/** set params for services */
$container->getDefinition('my.managed.service.one')
->addMethodCall('setContainerParams', array($container->getParameterBag()->all()));
$container->getDefinition('my.managed.service.etc')
->addMethodCall('setContainerParams', array($container->getParameterBag()->all()));
}
}
Please note that we can not inject ParameterBag object directly, cause it throws:
[Symfony\Component\DependencyInjection\Exception\RuntimeException]
Unable to dump a service container if a parameter is an object or a
resource.
Tested under Symfony version 2.3.4
Related
maybe i try the impossible thing, but i try so archive a logging system where i can log anywhere with something like Logger::info('some info') or plain LogInfo(...).
So no pre-initialisation with new .. or as parameter/injection (have to us it in some functions with "dynamic" count of parameters). I need to use inside a controller/command and outside in smaller custom made classes and functions, called by some way by the controllers/commands but not extending any containers.
for example i use the MessageGenerator Example from the Symfony Docs
// srcMessageGenerator.php
namespace App\Library\Util;
use Doctrine\ORM\EntityManagerInterface;
class MessageGenerator
{
private $em;
public function setEM(EntityManagerInterface $em){
$this->em = $em;
var_dump('a');
}
public function getHappyMessage(){
var_dump($this->em);
$messages = [
'You did it! You updated the system! Amazing!',
'That was one of the coolest updates I\'ve seen all day!',
'Great work! Keep going!',
];
$index = array_rand($messages);
return $messages[$index];
}
}
I already tried to define it as service
# config/services.yaml
services:
App\Library\Util\MessageGenerator:
arguments: ['#doctrine.orm.entity_manager']
public: true
problem:
i have to inject it to every function i wanna use it - not possible
i tried use it as an static function - services can't be static
i tried to isolate it as static function without beeing a service, but then i don't know how to get the config/package/doctrine settings depending on my env.
i have looked into monolog too, but here is the same problem, i cant use it without declaration outside a controller/command.
anyone have a hint how i can access the default (env based) doctrine connection, or how i can use a service inside/outside a controller/command without declaring it (static like) and giving the entity manager to it.
(a solution to second way would be lovely, so i can "upgrade" to monolog some day using the same solution)
You can use Injecting Services/Config into a Service in Symfony by passing your service to the function in your controller.
Your service:
class MessageGeneratorService
{
private $em;
public function __construct(EntityManagerInterface $em){
$this->em = $em;
}
public function getHappyMessage(){
// the job
}
}
In an other service:
class MyService
{
private $messageGeneratorService;
public __construct(MessageGeneratorService $mGService) {
$this->messageGeneratorService = $mGService
}
// You can use this service like you use MessageGeneratorService
public makeMeHappy() {
$messageGeneratorService->getHappyMessage();
}
}
In a controller:
class MyController
{
/**
* #Route("/example", name="page_example")
*/
public index(MessageGeneratorService $mGService) {
$mGService->getHappyMessage(); // Now, you can use it
return $this->render('example/index.html.twig');
}
}
Now, you can use an instance of MessageGeneratorService in a controller or in a service:
The container will automatically know to pass the logger service when
instantiating the MessageGenerator
In my Symfony project I'll have lot of classes that have similar dependecies (however, the classes are not directly related to each other). For example, most of them requires access to EventBus.
In other framework I was able to specify an interface for the class, for example:
interface EventBusAwareInterface
{
public setEventBus(EventBus $bus);
public getEventBus() : EventBus
}
and then configure DI container to recognize such objects that implements this interface, and call their setEventBus() method with proper argument.
I wonder if there's a method to do the same in Symfony4.
You can use _instanceof directive in your services.yaml like that:
services:
_instanceof:
App\EventBusAwareInterface:
calls:
- method: setEventBus
arguments:
- '#event.bus.service'
My original comment was not quire correct. You can use #inject but it seems to require an additional jms bundle. Could have sworn the container supported it out of the box but I guess not.
However, autowire supports a #required annotation which seems to do the trick.
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\RouterInterface;
trait RouterTrait
{
/** #var RouterInterface */
protected $router;
/**
* #param RouterInterface $router
* #required
*/
public function setRouter(RouterInterface $router)
{
$this->router = $router;
}
// Copied directly from Symfony ControllerTrait
protected function generateUrl(
string $route,
array $parameters = array(),
int $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH): string
{
return $this->router->generate($route, $parameters, $referenceType);
}
protected function redirect($url, $status = 302) : RedirectResponse
{
return new RedirectResponse($url, $status);
}
protected function redirectToRoute($route, array $parameters = array(), $status = 302) : RedirectResponse
{
return $this->redirect($this->generateUrl($route, $parameters), $status);
}
}
Now, any autowired service that uses the RouterTrait will automatically get the router injected.
Yes, something even simpler is very possible. However, I would not encourage over-usage as it can very quickly introduce things like method name collisions and reduce code readability.
That said, Symfony introduced service auto-wiring concept starting with 3.3 (I think), which can be used to have dependency injection with zero-config. In PHP, interfaces cannot contain implementation, but, traits can. So, you could do something like this:
trait FooTraitHandler
{
/**
* #var LoggerInterface
*/
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
}
And then:
class RealService
{
use FooTraitHandler;
public function multiply($a, $b)
{
$this->logger->log(LogLevel::ALERT, "Doing some basic math!");
return $a * $b;
}
}
And finally, for example, your controller could inject this RealService service and use multiply method as usual.
So, couple of things worth mentioning:
You do not need pair of getter/setter - trait's member is visible in class utilizing it.
You can utilize many traits, achieving just what you wanted with many interfaces
Finally, if some of utilized traits have methods with same name, you'll get a fatal error. As per official docs:
If two Traits insert a method with the same name, a fatal error is produced, if the conflict is not explicitly resolved. To resolve naming conflicts between Traits used in the same class, the insteadof operator needs to be used to choose exactly one of the conflicting methods.
But, in my opinion, doing so deteriorates code readability substantially.
Hope this helps...
I create a service that uses FilesystemCache, I don't want to make a new FilesystemCache each time I call the service so I have an argument in the service constructor where I can give an instance.
What I have so far:
Service class:
class MyService
{
private $cache;
private $url;
/**
* MyService constructor.
* #param FilesystemCache $cache
*/
public function __construct(FilesystemCache $cache)
{
$this->cache = $cache;
}
private function data()
{
if ($this->cache->has('data')) {
$data = $this->cache->get('data');
} else {
$data = file_get_contents("my/url/to/data");
}
return $data;
}
}
Config:
services:
# Aliases
Symfony\Component\Cache\Adapter\FilesystemAdapter: '#cache.adapter.filesystem'
# Services
services.myservice:
class: AppBundle\Services\MyService
arguments:
- '#cache.adapter.filesystem'
Where I use the Service:
$myService = $this->container->get('services.myservice');
But what I get is an error:
The definition "services.myservice" has a reference to an abstract definition "cache.adapter.filesystem". Abstract definitions cannot be the target of references.
So, my question is how I have to modify my service or my declaration, or whatever, to be able to do what I want to do: not create an instance each time I call the service.
I highly recommand you to use the cache.app service instead of your own filesystem.cache. Also, you could create your own Adapter.
To make that posible I had to register a new service with the class I want to use in my service constructor. So, my services.yml would be like:
services:
filesystem.cache:
class: Symfony\Component\Cache\Simple\FilesystemCache
services.myservice:
class: AppBundle\Services\MyService
arguments:
- '#filesystem.cache'
and now I'm able to use my service without getting an error.
Using the standard S3.3 autowire setup, this worked for me:
// services.yml
// This basically gives autowire a concrete cache implementation
// No additional parameters are needed
Symfony\Component\Cache\Simple\FilesystemCache:
// And there is no need for any entry for MyService
....
// MyService.php
use Psr\SimpleCache\CacheInterface;
public function __construct(CacheInterface $cache)
This of course will only work if you only have one concrete implementation of CacheInterface in your container.
I have a FooService that's fetches some data from another config service.
I have that config service as the config is different depending on the request data. The config service has the RequestStack injected and builds the relevant config array.
My FooService looks as follows:
class Foo
{
private $config;
/**
* #param Config $config
*/
public function __construct(Config $config) {
$this->config = $config->getData();
}
}
Yet instead of injecting the service, I prefer to inject only the method call result of getData, as it is simpler for unit testing. I won't need to mock the config, I can just pass an array.
My current service.yml has a definition like:
config:
...
foo:
class: Kopernikus\LeBundle\Service\Foo
arguments:
- #config
I tried changing my foo definition like for factory methods via:
foo:
class: Kopernikus\LeBundle\Service\Foo
arguments:
- [#config, getData]
yet this too injects the whole service.
How to inject method result of a service as an argument for another service?
You can achieve this with the expression language (New in Symfony 2.4)
As described in the doc here you can do something as example:
config:
...
foo:
class: Kopernikus\LeBundle\Service\Foo
arguments: ["#=service('config').getData()"]
Hope this help
You're basically asking for a dependency injection container. You can make your own that has a simple function (such as) get() that returns a new instance of Foo? You can define it to create new objects upon each invocation of get(), or more of a singleton that always returns the same object (by storing it in a private static variable).
eg,
<?php
class FooContainer implements YourContainerInterface {
public static function get() {
// Config logic here
return new Foo($config);
}
}
For example i have algorithmic function, which calculates specific hash-code. Function itself is 300+ lines of code. I need to use that functions many times in many different controllers in my bundle. Where can i store my calculate_hash() to use it in my bundle ? Can i access it from other bundles ?
Can i also write global calculate_hash() which have access to entity manager ?
Didn't find my answer here.
In the Symfony2 world, this is clearly belonging to a service. Services are in fact normal classes that are tied to the dependency injection container. You can inject them the dependencies you need. For example, say your class where the function calculate_hash is located is AlgorithmicHelper. The service holds "global" functions. You define your class something like this:
namespace Acme\AcmeBundle\Helper;
// Correct use statements here ...
class AlgorithmicHelper {
private $entityManager;
public function __construct(EntityManager $entityManager) {
$this->entityManager = $entityManager;
}
public function calculate_hash() {
// Do what you need, $this->entityManager holds a reference to your entity manager
}
}
This class then needs to be made aware to symfony dependecy container. For this, you define you service in the app/config/config.yml files by adding a service section like this:
services:
acme.helper.algorithmic:
class: Acme\AcmeBundle\Helper\AlgorithmicHelper
arguments:
entityManager: "#doctrine.orm.entity_manager"
Just below the service, is the service id. It is used to retrieve your service in the controllers for example. After, you specify the class of the service and then, the arguments to pass to the constructor of the class. The # notation means pass a reference to the service with id doctrine.orm.entity_manager.
Then, in your controller, you do something like this to retrieve the service and used it:
$helper = $this->get('acme.helper.algorithmic');
$helper-> calculate_hash();
Note that the result of the call to $this->get('acme.helper.algorithmic') will always return the same instance of the helper. This means that, by default, service are unique. It is like having a singleton class.
For further details, I invite you to read the Symfony2 book. Check those links also
The service container section from Symfony2 book.
An answer I gave on accesing service outside controllers, here.
Hope it helps.
Regards,
Matt
Braian in comment asked for Symfony 3 answer, so here is one Symfony 3.3 (released May 2017):
1. The original class remains the same
namespace Acme\AcmeBundle\Helper;
use Doctrine\ORM\EntityManager;
final class AlgorithmicHelper
{
/**
* #var EntityManager
*/
private $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function calculateHash()
{
// Do what you need, $this->entityManager holds a reference to your entity manager
}
}
2. Service registration is much simpler
# app/config/services.yml
services:
_defaults: autowire # this enabled constructor autowiring for all registered services
Acme\AcmeBundle\Helper\AlgorithmicHelper: ~
3. Use constructor injection to get the service
use Acme\AcmeBundle\Helper\AlgorithmicHelper;
class SomeController
{
/**
* #var AlgorithmicHelper
*/
private $algorithmicHelper;
public function __construct(AlgorithmicHelper $algorithmicHelper)
{
$this->algorithmicHelper = $algorithmicHelper;
}
public function someAction()
{
// some code
$hash = $this->algorithmicHelper->calculateHash();
// some code
}
}
You can read about Symfony 3.3 dependency injection (in this case registering services in config and using it in controller) news in these 2 posts:
https://www.tomasvotruba.cz/blog/2017/05/07/how-to-refactor-to-new-dependency-injection-features-in-symfony-3-3/
https://symfony.com/blog/the-new-symfony-3-3-service-configuration-changes-explained