Inject specific Doctrine entity in Symfony2 service - php

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.

Related

How to mock doctrine service for controller action when doctrine is used elsewhere

I have a controller action that contains some code like the following:
$repository = $this->get("doctrine")->getRepository(User::class);
$user = $repository->findOneBy(array('username' => $request->request->get("username")));
I wanted to mock the repository. At first I wasn't sure how to do that, but then I found this SO post: Testing Controllers in Symfony2 with Doctrine
From the answer there, I surmised that I should create a mock of the doctrine service AND the repository object, and tell the mockbuilder that the repository object returns the entity I want to test. Then I should replace the doctrine service using the following line of code:
$client->getContainer()->set("doctrine", $doctrineMockObject);
and then make the request:
$client->request("POST", "/checkUsername");
The problem with this is that there is a twig template that actually calls a separate controller action, and that action uses doctrine as well. So that causes the application to break since it is using the mock doctrine object I injected into the container.
Is there any way to only use the mock doctrine service for the action that I am testing? Otherwise, is there any alternative method to do what I want to do? I am out of ideas.
As 'alternative method' you can stop using Container as Service Locator and instead of injecting it everywhere inject only services that you need into controllers/other services.

The Symfony Dependency Injection Container should not be passed as an argument

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!

Symfony - Changing how controllers are instantiated and executed

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

Symfony2 Passing instantiated Object to Service, rather than new object

im looking to pass an already instatiated object to my service constructer, rather than creating a new one, to create a new one i do this:
services:
ExampleService:
class: Ex\ModelBundle\lib\Ex\ExSave
LoginService:
class: Snap\ModelBundle\Service\Login
arguments: ["#doctrine.orm.entity_manager", "#ExampleService"]
How would i pass an already instatiated ExampleService to LoginService
By default all services have container scope which according to docs mean:
The same instance is used each time you request it from this container.
So, you create your ExampleService, but any other, subsequent, request for that service is going to return its previously created instance.
It all boils down to point that multiple services can (and will) use shared instance of your ExampleService.
Alternately, you could use Setter Injection.
Basically, you don't pass #ExampleService to __construct but rather to separate setter which will set the property value (within the service). Then you use your service as usual.

Adapters and Dependency Injection

I am currently building an MVC application in PHP (not using any frameworks). I am using yadif (https://github.com/beberlei/yadif) for dependency injection.
I would like to build a login module. It should be able to use adapters, for example one might be able to set that logins are authenticated using the MySql database or some LDAP directory. This setting would be done in the admin area and is stored in a database.
I imagine that I would have an abstract adapter:
<?php
abstract AbstractLoginAdapter{
abstract function login($username, $pass){}
}
I would then just implement adapters like so:
<?php
MySQLLoginAdapter extends AbstractLoginAdapter{
public function login($username, $pass){
//do stuff
}
}
That's all nice and well, but how do I create an instance of the adapter? Usually, dependencies would be injected using yadif via the constructor:
<?php
class loginController{
private $_adapter;
public function __construct(AbstractLoginAdapter $adapter){
$this->_adapter = $adapter;
}
}
However, since I don't know which concrete adapter will be injected, I can't set that in a configuration before hand. Yadif allows me to create a configuration which I then need to pass to the container:
$builder = new Yadif_Builder();
$builder->bind("loginController")
->to("loginController")
->args($SelectedLoginAdapter);
Since the application uses a front controller, a DI container is created there. It then creates a routing object etc.
In light of this, should I pass a reference of that container down to the loginController object, and then use that container to instantiate my adapter?
Or should I instantiate a new container within my loginController object and then just load in an instance of the adapter?
I would do the first: pass a reference down to your controller. You'll want to use a single Dependency Injector Container (DIC) in your application. You don't want to create a new DIC whenever you need access to it. That would lead to duplication of objects stored in the DIC.
I know this is how Symfony 2 does it. All controllers (and many other classes) implement the ContainerAware interface. That interface has a single method setContainer() that is used to pass down a reference to the DIC.
I don't know about your specific DI tool but from a DI point of view you would be specifying which type to use. The container itself is responsible for instantiating a new instance (and possibly of all the dependencies of that type as well) of the configured type.
The benefit of DI in your example would be that you could deploy exactly the same code with a different configuration with 1 installation using LDAP and the other using MySQL authentication.
Refactor type hinting ("AbstractLoginAdapter") to ("MySQLLoginAdapter").
If you call abstract class method in the new __CLASS__ // Fatal Error.

Categories