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.
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
I am trying to implement UserManager from FOSUserBundle (Symfony3.4).
Service/Register.php
<?php
namespace AppBundle\Service;
use FOS\UserBundle\Model\UserManager;
class Register
{
private $userManager;
public function __construct(UserManager $userManager)
{
$this->userManager = $userManager;
}
public function register() {
$user = $this->userManager->findUserByUsernameOrEmail('aaa#gmail.clom');
if($user){
return false;
}
return true;
}
}
When I try call this method I get:
Cannot autowire service "AppBundle\Service\Register": argument "$userManager" of method "__construct()" references class "FOS\UserBundle\Model\UserManager" but no such service exists. You should maybe alias this class to the existing "fos_user.user_manager.default" service.
What should I do now?
I had a similar problem (in Symfony 4, but the principles should apply to 3.4) with a different service and managed to find the answer on the Symfony doc page Defining Services Dependencies Automatically (Autowiring).
Here's an extract from that page:
The main way to configure autowiring is to create a service whose id exactly matches its class. In the previous example, the service's id is AppBundle\Util\Rot13Transformer, which allows us to autowire this type automatically.
This can also be accomplished using an alias.
You need an alias because the service ID doesn't match the classname. So do this:
# app/config/services.yml
services:
# ...
# the `fos_user.user_manager.default` service will be injected when
# a `FOS\UserBundle\Model\UserManager` type-hint is detected
FOS\UserBundle\Model\UserManager: '#fos_user.user_manager.default'
I need to pass ZipArchive class as a dependency for one of my class. I hope it is possible to do but no idea how to define in services.yml file.
Any idea please.
I'd instantiate the core php object in the service construct and provide a setter to override this object (ie for testing later on):
services.yml:
app.my_service:
class: AppBundler\Services\MyService
AppBundle\Services\MyService:
namespace AppBundle\Services;
class MyService
{
protected $ziparchive;
public function __construct()
{
$this->ziparchive = new ZipArchive();
}
public function setZipArchive(\ZipArchive $zipArchive)
{
$this->zipArchive = $zipArchive;
return $this;
}
}
You can pass your ZipArchive class as service's argument
First, you need to create service for ZipArchive
services:
lib.ziparchive:
class: Lib\ZipArchive #your ZipArchive Class Namespace
Then you can pass lib.ziparchive to your class service
app.my_service:
class: AppBundler\Services\MyService
arguments: ['lib.ziparchive']
In your MyService Class, you need to create constructor.
namespace AppBundle\Services;
use Lib\ZipArchive
class MyService
{
protected $ziparchive;
public function __construct(ZipArchive $ziparchive)
{
$this->ziparchive = $ziparchive;
}
}
Check more detail for service container
Hope it help.
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);
}
}
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