This morning, SensioLabs Insight removed the platinum badge from one of my distributed bundles.
The Symfony Dependency Injection Container should not be passed as an argument.
/**
* Sets the Container.
*
* #param ContainerInterface|null $container
*/
public function setContainer(ContainerInterface $container = null)
A Symfony dependency injection container has been found as an argument.
The method is part of the following service:
rch_jwt_user.credential_fetcher:
class: RCH\JWTUserBundle\Service\CredentialFetcher
calls:
- [ setContainer, [ "#service_container" ] ]
I know I can pass the container as constructor argument like this:
rch_jwt_user.credential_fetcher:
class: RCH\JWTUserBundle\Service\CredentialFetcher
arguments: [ "#service_container" ]
But a lot of community bundles use a setter rather than the constructor.
So what is the reason of this major minor warning ?
An explanation of why/when should we prefer use a setter rather than pass the container as an argument in constructor (and the inverse) would be really appreciated.
EDIT
In response of comments:
SLI don't care about how the container is injected, apparently it's a bad practice to inject it.
Any idea of an SLI-compliant alternative ?
Thanks to the help of #RenatoMendesFigueiredo, #hasumedic & (the most part) #Cerad, without need of premium plan, I can see the point of this warning and figure it out.
In fact, a long time ago, I would make my service extending the ContainerAware rather than write the method directly, but forced to live with its limits (the service could not have extended another class).
In 2.4, the ContainerAwareTrait was added.
My guess is that the warning has been added in the same time as this trait, because :
1) No reason to write a setter or a constructor to inject the container if a trait that do it already exists.
2) No need of pass the container as an argument in an another goal than inject it to a service.
Also, I removed the setContainer method from the class of my service, make it use the ContainerAwareTrait and keep the the service definition as before.
And ...
Kudos!
Related
Maybe it's a silly question, but the official documentation is not clear for me at that place. It says that dependency injection is automatically resolved e.g. in Controllers. But in my routing, I bind my route to a class which doesn't inheritance from Controller class. And automatic injection works! Does it mean that resolving every route automatically uses ServiceContainer and resolves dependencies?
In docs we have:
Alternatively, and importantly, you may "type-hint" the dependency in the constructor of a class that is resolved by the container, including controllers, event listeners, middleware, and more. Additionally, you may type-hint dependencies in the handle method of queued jobs. In practice, this is how most of your objects should be resolved by the container.
And it says "controllers, event listeners, middleware, AND MORE". Could you show me other places, where autoinjection works?
I'm using Laravel >5.8. Thank you.
Dependency injection depends on how you call a function/method, not the function itself.
[...] you may "type-hint" the dependency in the constructor of a class that is resolved by the container
"Resolved by the container" means that you (or in this case the Laravel router) are calling it through the container.
To automatically resolve the dependencies in the constructor you can use app()->make():
$myThing = app()->make(MyClass::class);
This will give you an instance of MyClass with the constructor dependencies resolved.
To use DI on a method you can use app()->call():
$result = app()->call([$myThing, 'someMethod']);
In general, Laravel uses DI almost everywhere. I always assume it works, and if it doesn't you can simply resolve the dependency manually.
Note: The
is based on Laravel 5.4.26 but most, if not all, information should still be correct.
I found this answer useful, so thank you! I am learning more and more about DI. However, I am also somewhat wondering why 'new' cannot be more fully resolved. See below:
// SupportLevel1 is an interface
class LevelBClass {
private $slb = NULL;
public function __construct(SupportLevel1 $slb){
$this->slb = $slb;
}
};
//// THE FOLLOWING DOES NOT WORK. NEED TO SUPPLY
//// ARG TO CONSTRUCTOR.
//// $objLevelBClass = new LevelBClass();
// This method works, but requires *some* manual intervention.
$objLevelBClass = new LevelBClass(app(SupportLevel1::class));
/*
* Here the technique is to use the helper app and its
* resulting make method. However, we give it
* the top level class - that is LevelBClass - which
* we know will need to resolve the interface
* argument to its constructor. From a top down, stand
* back approach, it works like a charm!
*/
$objLevelBClass = app()->make(LevelBClass::class);
So I've got a Symfony controller, and I'm injecting needed services into my methods via params.
One of the params (MySqlGroupDAO $groupDAO) is used by all methods of this controller class.
Currently, I'm passing the 'common' param as my last param in each method like this:
/**
* #Route("/{id}", methods={"POST"})
* #IsGranted("EDIT_GROUP", subject="parentGroup")
*/
public function addGroup(Request $request, MySqlGroupDAO $groupDAO) {
$group = new Group();
//code to init group from request
$groupDAO->addGroup($group);
return new Response("Adding $groupName");
}
Doing it this way allows me to eliminate my __construct method. However, I'm not sure this is the best way to go about it. Since it's common across all methods would it be better to re-add my constructor and do something like this?:
private $groupDAO;
public function __construct(
Config $config,
ValidatorInterface $validator,
TranslatorInterface $translator,
RequestStack $requestStack
) {
parent::__construct($config, $validator, $translator, $requestStack);
$this->groupDAO = new MySqlGroupDAO($config);
}
/**
* #Route("/{id}", methods={"POST"})
* #IsGranted("EDIT_GROUP", subject="parentGroup")
*/
public function addGroup(Request $request) {
$group = new Group();
//code to init group from request
$this->groupDAO->addGroup($group);
return new Response("Adding $groupName");
}
In doing so, I'm eliminating about half a dozen params across all my methods (in this particular class). But I'm adding back in my constructor, which requires me to add a class param, and inject several additional params in my constructor since it extends another class.
Is there an advantage to doing it one way vs the other?
Thanks.
Reasons to use DI in a controller's route methods:
Less susceptible to changes of the parent::__construct method. Using DI on the constructor means you have to adapt your code whenever this changes. Also notice that some Symfony bundles may assume that controllers have a particular signature and it might make things more complicated for you if it does not.
If at least one of the routes does not use the service, by using these fine-grained DIs we avoid instantiating a service when this is not necessary (which can be costly if it has its own DIs that weren't already used somewhere else). This can be mostly offset by using lazy services though.
Reasons to use DI in the contructor:
In services other than controllers (and in methods other than route methods of controllers, if any), you can't use autowiring. If you want to inject your dependencies using a method's argument, you'll have to manually pass that dependency with each call. This means in turn that whichever service calls that method should itself have a DI on the required service. The problem is therefore shifted, but it can't be shifted infinitely that way, and at some point you're going to want to use some autowiring on a parent.
Alternative to using DI in the constructor:
You can also use setter injection and configure your service this way. This is functionally pretty similar to using DI in the constructor, but it bypasses the major drawback of generating a different signature from the parent and being more work to maintain if the parent constructor changes.
You can also make this easier to use for services you often inject. Make the services that need this DI implement an interface and configure them with _instanceof. Symfony does that with its ContainerAwareInterface and even has a facilitator in the form of ContainerAwareTrait to declare the setter and the property. In your case, if several services require the MySqlGroupDAO service, you could define a MySqlGroupDAOAwareTrait and MySqlGroupDAOAwareInterface, add a MySqlGroupDAOAwareInterface entry in your services.yaml's _instanceof section, and use the trait and implement the interface in services that need the DI.
Edit Nov. 2021:
This answer is still being read by new people, so I thought I'd complete it with something that was added with Symfony 5.2 (and it requires PHP 8): using PHP attributes for dependency injection.
This allows Setter Injection without touching services.yaml, and it allows public property injection. Here's the doc's example for both of these:
use Symfony\Contracts\Service\Attribute\Required;
class SomeService
{
// Example of public property injection
#[Required]
public Bar $bar;
// Example of setter injection without editing services.yaml
#[Required]
public function setFoo(Foo $foo): void
{
// ...
}
}
Notice that the property and the setter method used above need to be public (which might or might not be okay with you).
Read more at https://symfony.com/blog/new-in-symfony-5-2-php-8-attributes.
Note: as of version 2.8, Symfony provided autowire: true for service configuration, and as of version 3.3, Symfony provided alias (instead of autowire_types) to alias a concrete object to an interface for automatic dependency injection into 'controllers as services'. There's also a bundle to allow autowiring for controller 'action' methods, although I've moved away from this and have focussed more on a variation of the ADR pattern (which is, basically, a single 'action' class with an interface method and not shoving a load of actions methods within a single class which eventually makes for an architectural nightmare). This is, effectively, what I've been looking for all these years and now no longer need to 'hook-in' a decent recursive dependency injector (auryn) as the framework now handles what it should have four years previous. I'll leave this answer here in case anyone wants to trace the steps that I did to see how the kernel works and some of the options available at this level.
Note: Although this question primarily targets Symfony 3, it should also be relevant to users of Symfony 2 as the kernel logic doesn't seem to have changed much.
I want to change how controllers are instantiated in Symfony. The logic for their instantiation currently resides in HttpKernel::handle and, more specifically, HttpKernel::handleRaw. I want to replace call_user_func_array($controller, $arguments) with my own injector performing that specific line instead.
The options I have tried thus far:
Extending HttpKernel::handle with my own method and then having this called by symfony
http_kernel:
class: AppBundle\HttpKernel
arguments: ['#event_dispatcher', '#controller_resolver', '#request_stack']
The downside of this is that, because handleRaw is private, I can't extend it without hacky reflection and so I would have to copy and paste a tonne of code.
Creating and registering a new controller resolver
controller_resolver:
class: AppBundle\ControllerResolver
arguments: []
This was a fundamental misunderstanding I had so I thought I'd document it here. The resolver's job is to resolve where to find the controller as a callable. It hasn't actually been called yet. I am more than happy with how Symfony takes the routes from routes.yml and figures out the class and method to call for the controller as a callable.
Adding an event listener on kernel.request
kernel.request:
class: MyCustomRequestListener
tags:
- { name: kernel.event_listener, event: kernel.request, method: onKernelRequest, priority: 33 /** Important, we'll get to why in a minute **/ }
Taking a look at the Http Kernel Component Documentation, we can see that it has the following typical purpose:
To add more information to the Request, initialise parts of the system, or return a Response if possible (e.g. a security layer that denies access).
I figured that by creating a new listener, using my custom injector to create my controller, and then return a response in that listener, would bypass the rest of the code that instantiates the controller. This is what I want! But there's a major flaw with this:
The Symfony Profiler doesn't show up or any of that stuff, it's just my response and that's it. Dead. I found that I can switch the priority from 31 to 33 and have it switch between my code and Symfonys, and I believe this is because of the router listener priority. I feel I'm going down the wrong path here.
Listening on the kernel.controller event.
No, this allows me to change the callable that will be called by call_user_func_array(), not how the controller is actually instantiated, which is my goal.
I've documented my ideas but I'm out. How can I achieve the following?
Change how the controllers are instantiated and then executed, specifically call_user_func_array() which is in a bloody private method (thanks Symfony)
Fall back to the default controller instantiation if mine doesn't work
Allow everything else to work as expected, like the profiler loading
Be able to bundle this up with an extension for other users
Why do I want to do this?
Controllers can have many different methods for different circumstances and each method should be able to typehint for what it individually requires rather than having a constructor take all the things, some of which may not even be used depending on the controller method being executed. Controllers don't really adhere to the Single Responsibility Principle, and they're an 'object edge case'. But they are what they are.
I want to replace how controllers are created with my own recursively autowiring injector, and also how they are executed, again with recursive introspection via my injector, as the default Symfony package does not seem to have this functionality. Even with the latest "autowire" service option in Symfony 2.8+.
The controller resolver actually does two things. The first is to get the controller. The second is to get a list of arguments for a given action.
$arguments = $this->resolver->getArguments($request, $controller);
$response = call_user_func_array($controller, $arguments);
It is the getArguments method that you could override to implement your special "action method injection" functionality. You just need to determine what arguments the action method needs and return an array of them.
Based on a different question, I also think you might be misunderstanding the autowire functionality. Autowire really only applies to constructor injection. It's not going to help with action method injection.
If the getArguments does not solve your requirement then overriding the handle method is really your only option. Yes there is quite a bit of code to copy/paste from handleRaw but that is because there is quite a bit to do in there. And even if handleRaw was protected you would still have to copy/paste the code just to get at the one line you want to replace.
Why don't you return your own callable from custom ControllerResolverInterface that would instantiate Controller in a way you want and call it?
It would be basically a decorator.
You can extend Symfony\Component\HttpKernel\Controller\ControllerResolver with your own implementation of instantiateController() method, or you can implement ControllerResolverInterface from the scratch.
UPD:
When Symfony makes a call_user_func_array($controller, $arguments); call in handleRaw(), the $controller variable is what you've returned from your custom ControllerResolver. That means you can return any callable from your resolver (it can be [$this, "callController"] f.e.) and inside this callable you would create a new Controller with Auryn and call it.
UPD2:
If you're still struggling with this, I'll add an example because you might miss what I meant here.
use Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver;
class AutowiringControllerResolver extends ControllerResolver
{
// ... constructor stuff; assume $injector is a part of this class
protected function createController($controller)
{
$controller = parent::createController($controller);
return function (...$arguments) use ($controller) {
// you can do with resolved $arguments whatever you want
// or you can override getArguments() method and return
// empty array to discard getArguments() functionality completely
return $this->injector->execute($controller);
};
}
protected function instantiateController($classname)
{
return $this->injector->make($classname);
}
}
Listener is too late to solve your needs, since it excludes Dependency Injection container, which is crucial to create a valid object (~= service).
You are probably looking for Controller Autowiring feature.
If that's so, you might find solution or at least inspiration in this bundle: http://www.tomasvotruba.cz/blog/2016/03/10/autowired-controllers-as-services-for-lazy-people/
It meets your needs in these points:
autowire injector
fallback to default (FrameworkBundle's) controller resolver, if not found
it also should keep all the flow working, since there are no hacks during controller resolving process
I am currently testing phalcon php for a project, and I am looking for a way to automatically inject certain classes automatically based on an implemented interface.
The Dependency Injection reference has an example where if a class implements Phalcon\DI\InjectionAwareInterface, it will automatically inject the DI into that class.
What I want to do is similar. If a class has for instance Aranea\Db\DbAdapterAware, it should automatically inject the DbAdapter in that class. I am looking for something similar to what Zend Framework 2 does (https://juriansluiman.nl/article/121/interface-injection-with-initializers-in-zend-servicemanager), where during DI config you can specify initializers like this:
'initializers' => array(
'logger' => function($service, $sm) {
if ($service instanceof LoggerAwareInterface) {
$logger = $sm->get('logger');
$service->setLogger($logger);
}
}
),
If this is not automatically possible in PhalconPHP, I was thinking of overriding the FactoryDefault class and implement it myself. What would be the right place to inject this logic? In the get* methods, or rather in the set* methods? I assume that a method is not initialized during DI initializing but on first call, so get* would sounds more appropriate?
Thanks for your advice,
Jeroen
The Dependency Injection reference has an example where if a class implements Phalcon\DI\InjectionAwareInterface, it will automatically inject the DI into that class.
That is not entirely true, what it means is that the DI gets (automatically) injected when the service is resolved given it implements this interface, the DI doesn't magically appears there just because the class implements some interface.
If a class has for instance Aranea\Db\DbAdapterAware, it should automatically inject the DbAdapter in that class.
That is sort of how it works (not technically) if your class extends the Phalcon\DI\Injectable (or implements the InjectionAwareInterface in the same way as Phalcon\DI\Injectable). Inside Injectable there is a __get magic, which returns the service from the DI if the service exists. In other words stuff get injected only in the DI, and other classes lookup for services in there.
To inject your own services you can either pass them in your configuration to the DI or extend the DI or FactoryDefault. The difference between the two is that FactoryDefault already comes preconfigured with the useful services, which you might not need though.
I assume that a method is not initialized during DI initializing but on first call, so get* would sounds more appropriate?
Yes, there is a Phalcon\DI\Service object that represents the service and resolved when called for the first time (if it's a shared service) or resolved every time (if it's not). You normally would want all your services to be shared, otherwise this often becomes a bottleneck, e.g., when resolving a non-shared database adapter, which establishes the connection every time you call it…
PS: Note, for it to work as you want it with the DbAdapter you can do a few things:
Add the adapter getter and return DI::getDefault()->getShared('db');
Extend the Phalcon\DI\Injectable and set the DI when the class is created, so it can lookup for services.
Every time you need the adapter simply get it from the DI like shown in the first option.
I'm currently using an entity with a specific ID in a service. To make this work using dependency injection, I am injecting the entity repository and a specific ID. For example:
public function __construct($myRepo, $myId) {
$this->myEntity = $myRepo->find($myId);
}
Is there a way to directly inject this specific entity without passing in the repository? For example, the ideal method would be:
public function __construct($myEntity) {
$this->myEntity = $myEntity;
}
I can't figure out how to define the services that are needed to do this.
Services should be stateless. Saving a user with a specific id in a service makes them statefull.
Instead of passing the entity in the constructor and saving it in a property, the entity should be passed to the method that is called. E.g. instead of a mailer with the to address passed into the constructor, it should be passed to the mailer#mail() method.
#Wouter makes a good point about services being stateless. However, you can use the DI factory capability to accomplish your goal:
specific_entity:
class: SomeEntity # Not used but you still need something here
factory_service: specific_entity_repository
factory_method: find
arguments:
- 42
This will call the find method on your repository with an argument of 42. The returned entity will then be injected. I'm assuming you already know how to setup a repository as a service.