Symfony 4 - how dependecy injection works - symfony

Using Symfony 3.4 we call services like so: $container->get('service.name')
so we make sure to give names to our services.
in Symfony 4 we inject services by class name or interface directly in the controller like so:
public function someAction(HttpClientInterface $service){
// do something here
}
so what confuses me here is that we are injecting an interface and symfony takes care of invoking the right object in the controller.
my question is: if I have 2 services that implements the same interface :
class ClassA implements InterfaceX{}
class ClassB implements InterfaceX{}
and in the controller I do this:
public function someAction(InterfaceX $service){
// do something here
}
which service gets invoked ?

Yes, services can implement the same interface and in your controller you will need to inject correct service, like this:
public function someAction(ClassA $serviceA){
// do something here
}
and
public function someOtherAction(ClassB $serviceB){
// do something here
}
If you need same service in multiple methods on the same class, use Dependency Injection on the __construct

Related

Moving Controller as service to to other service on top level.....symfony2

If I have a controller which is with the name space
Src/ApiMap/ApiMapBundle/controller/Getapicontroller
class GetApiController extends Controller {
fitch data on the bases of entities define in entites classes and then save that data in in table
and save it in database
}
Now I define this as service in
App/config/service.yml
services:
app.get_controller:
class: ApiMaps\ApiMapBundle\Controller\GetApiController
3rd I define a service class on the location
app/container/getApi.php
Now I want to access this controller-service in this class I means in this service how can i access because if i use
this->get function
in simple class what would i need for it.
the point is i can not move my logic to service bcz it is using entities and other stuff in controller i want to move this logic to some 3rd class service...
it location is right now
src/container
getapi.php
and its definition is right now...
<?php
namespace dino_container\GetApi;
class GetApi {
public function __construct() {
i want to incorport this controller service in this class...
}
Seems like we had this conversation before.
It makes no sense to try calling a controller method from a command. A controller method's job is to convert a Request into a Response. Commands have no request object nor would they know what to do with a Response object.
Instead you want to share functionality between a controller and a command by extracting the functionality into it's own service. You then access that service from both the command and the controller.
# services.yml
my_service:
class: MyService
arguments: ['#doctrine.orm.default_entity_manager']
# Controller
public function myAction(Request $request)
{
$myService = $this->container->get('my_service');
$myService->doSomethingShared();
# Command
public function execute()
{
$myService = $this->container->get('my_service');
$myService->doSomethingShared();

How exactly can I define a controller as service using annotations?

This seems to be the fastest and simpliest way to use a controller as service, but I am still missing a step because it doesn't work.
Here is my code:
Controller/service:
// Test\TestBundle\Controller\TestController.php
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* #Route(service="test_service")
*/
class TestController extends Controller {
// My actions
}
Use :
// Test\TestBundle\Controller\UseController.php
// ...
public function useAction() {
$testService = $this->get('test_service');
}
When I do that, I get the error
You have requested a non-existent service "test_service".
When I check the list of services with app/console container:debug, I don't see my newly created service.
What am I missing?
From Controller as Service in SensioFrameworkExtraBundle:
The #Route annotation on a controller class can also be used to assign the controller class to a service so that the controller resolver will instantiate the controller by fetching it from the DI container instead of calling new PostController() itself:
/**
* #Route(service="my_post_controller_service")
*/
class PostController
{
// ...
}
The service attribute in the annotation is just to tell Symfony it should use the specified service, instead of instantiating the controller with the new statement. It does not register the service on its own.
Given your controller:
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
/**
* #Route(service="test_service")
*/
class TestController
{
public function myAction()
{
}
}
You need to actually register the controller as a service with the test_service id:
services:
test_service:
class: Test\TestBundle\Controller\TestController
The advantage of this approach is that you can inject your dependencies into the constructor by specifying them in the service definition, and you don't need to extend the base Controller class.
See How to define controllers as services and Controller as Service in SensioFrameworkExtraBundle.
For future folks, if you decide to use controller-as-a-service, you should better inject your services into the controller via the constructor instead of getting them through a service locator. The former is considered to be an antipattern, while the latter allows easy unit testing and is simply more verbose.
So instead of:
public function useAction() {
$testService = $this->get('test_service');
}
You should:
private $yourService;
public function __construct(YourService $yourService)
{
$this->yourService = $yourService;
}
public function useAction()
{
$this->yourService->use(...);
}
Don't create shortcuts, write solid, maintainable code.
For Symfony 3.4, we don't need to register controllers as services because they are already registered as services with the default services.yml configuration.
You just have to write this :
// current controller
public function myAction() {
$test = $this->get(SomeController::class)->someAction($param);
}

I have no access to container in my controller

I am trying to access a service in a Symfony Controller
$session = $this->get('session');
But I get the next error:
PHP Fatal error: Call to a member function get() on a non-object
I thought that Symfony2 had the controllers defined as services by default.
Note: this question was originally asked by Dbugger, but he removed it for no reason, while it was already answered.
Using the container in controllers
get() is only a shortcut function provided by the Symfony base Controller class to access the container.
Your controller must extend this class to use this function:
namespace Acme\ExampleBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
// your actions
}
If you don't want to depend on this class (for some reasons) you can extend ContainerAware to get the container injected and use it like in the get() shortcut:
namespace Acme\ExampleBundle\Controller;
use Symfony\Component\DependencyInjection\ContainerAware;
class DefaultController extends ContainerAware
{
public function exampleAction()
{
$myService = $this->container->get('my_service');
// do something
}
}
Creating controllers on your own
Controllers are not defined as services per default, you can define them, but it's not needed to get the container. If a request is made, the routing framework determines the controller, which need to be called. Then the controller gets constructed and the container is injected via the setContainer() method.
But if you construct the controller on your own (in a test or anywhere else), you have to inject the container on your own.
$controller = new DefaultController();
$controller->setContainer($container);
// $container comes trough DI or anything else.

Inject service in symfony2 Controller

How can I inject a service (the service that I created) into my Controller?
A setter injection would do.
<?php
namespace MyNamespace;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class MyController extends Controller
{
public function setMyService(MyService $myService)
{
$this->myService = $myService;
}
public function indexAction()
{
//Here I cannot access $this->myService;
//Because the setter is not called magically!
}
}
And my route settings :
// Resources/routing.yml
myController_index:
pattern: /test
defaults: { _controller: "FooBarBundle:MyController:index" }
I'm setting the service in another bundle :
// Resources/services.yml
parameters:
my.service.class: Path\To\My\Service
services:
my_service:
class: %my.service.class%
When the route is resolved, the service is not injected ( I know it shouldn't ).
I suppose somewhere in a yml file, I have to set:
calls:
- [setMyService, [#my_service]]
I am not using this Controller as a service, it's a regular Controller that serves a Request.
Edit: At this point in time, I am getting the service with $this->container->get('my_service'); But I need to inject it.
If you want to inject services into your controllers, you have to define controllers as services.
You could also take a look at JMSDiExtraBundle's special handling of controllers — if that solves your problem. But since I define my controllers as services, I haven't tried that.
When using the JMSDiExtraBundle you DON'T have to define your controller as a service (unlike #elnur said) and the code would be:
<?php
namespace MyNamespace;
use JMS\DiExtraBundle\Annotation as DI;
use Path\To\My\Service;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class MyController extends Controller
{
/**
* #var $myService Service
*
* #DI\Inject("my_service")
*/
protected $myService;
public function indexAction()
{
// $this->myService->method();
}
}
I find this approach very nice because you avoid writing a __construct() method.
Since it's 2017 ending now and there is no tag for Symfony 3 or upcoming Symfony 4 (and I think there should not be), this problem is solvable in a native much better way.
If you are still struggling and somehow ended up on this page and not in Symfony docs, then you should know, that you do not need to declare controller as service, as it is already registered as one.
What you need to do, is check you services.yml:
# app/config/services.yml
services:
# default configuration for services in *this* file
_defaults:
# ...
public: false
Change public: false to public:true if you want all services to be public.
Or explicitly add a service and declare it public:
# app/config/services.yml
services:
# ... same code as before
# explicitly configure the service
AppBundle\Service\MessageGenerator:
public: true
And then in your controller you can get the service:
use AppBundle\Service\MessageGenerator;
// accessing services like this only works if you extend Controller
class ProductController extends Controller
{
public function newAction()
{
// only works if your service is public
$messageGenerator = $this->get(MessageGenerator::class);
}
}
Read more:
Public vs Private services
Services in Container
If you don't want to define your controller as a service, you can add a listener to the kernel.controller event to configure it just before it is executed. This way, you can inject the services you need inside your controller using setters.
http://symfony.com/doc/current/components/http_kernel/introduction.html#component-http-kernel-kernel-controller

How to use monolog from the class which inherit Controller class

I am using monolog
in class DefaultController extends Controller
such as
$logger = $this->get('logger');
$logger->info('Get Started');
I can call this->get('logger') from the class which inherits Controller class.
However I want to use logger from other class such as /Entity/User.php
How can I make it?
my reference is
http://symfony.com/doc/2.0/cookbook/logging/monolog.html
In general you can access services like the logger in classes where the container is not automatically injected ( i.e. Controller and Commands extending ContainerAwareCommand ) by using Dependency Injection.
Possible injection-types are property-,setter- and constructor injection. My example will cover constructor injection. you will first need to create a service for your class.
Assuming yml-configuration an example could look like this:
services:
your_service:
class: Vendor/YourBundle/NonControllerExtendingClass
arguments: ["#logger"] # inject logger service into constructor
In my example the 'logger' service is automatically injected in the NonControllerExtendingClass if it is called as a service. Make sure you have something like this in your Vendor/YourBundle/NonControllerExtendingClass:
use Symfony\Component\HttpKernel\Log\LoggerInterface;
// ...
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
now use the logger in your method like this:
public function someAction()
{
$this->logger->info('Im here');
}
The logger will only be injected if you call your class as a Service or inject the logger manually.
// ... gets service from the container
$my_service = $this-container->get('your_service');
// ... manual injection
$logger = $this->container->get('logger');
// alternatively create a logger object yourself i.e. $logger = new Logger();
$my_service = new Vendor/YourBundle/NonControllerExtendingClass($logger);
If you want to track entity changes with the logger you should use a Doctrine Event Listener or Subscriber.
Readmore about it in the documentation chapter - How to Register Event Listeners and Subscribers.
Because of separation of concerns, entities should not have dependencies on services. Depending on your needs, it might be wise to use the logger from the controller/service or whatever that calls the entities method that you want to log.
Generally speaking, you can define classes as services and inject the logger into that service. If you are not yet familiar with the service container and dependency injection, I would strongly advise you to read this chapter of the docs.
It might be a tricky subject to grasp. However, as it is such a vital component of symfony, it will be really worth your while to try to understand this.

Resources