I use Symfony components in own framework. I installed DI component and tried to get it in my BaseController but all the time I get the same error:
Call to a member function get() on null
My base controller:
abstract class BaseController implements ContainerAwareInterface
{
use ContainerAwareTrait;
...
}
And when I try to access $this->container->get('ser_id'); return call to a member function.
How to slove this?
Update:
only work if i include in my controller service container
$this->service_container = include __DIR__ .'/../../../container.php';
and use it like this:
$this->service_container->get('id');
Related
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
I have problem with injecting RequestStack.
In this case, if I use just Request, everything is fine:
<?php
namespace Cms\AdminBundle\Controller;
use Symfony\Component\HttpFoundation\Request;
...
class DefaultController extends BaseController {
public function mainAction(Request $request) {
...
But if I try to use RequestStack, I get error
"Controller "Cms\AdminBundle\Controller\DefaultController::mainAction()" requires that you provide a value for the "$requestStack" argument (because there is no default value or because there is a non optional argument after this one)."
Here is the code:
namespace Cms\AdminBundle\Controller;
use Symfony\Component\HttpFoundation\RequestStack;
...
class DefaultController extends BaseController {
public function mainAction(RequestStack $requestStack) {
...
Thank you!
According to Symfony docs:
In a controller for instance, don't inject the request stack, use the Request type-hint on an action method argument instead.
RequestStack is a service but Request is a value object (not a service) not accessible from the container.
So if you need request_stack in you controller, depending on your constoller dependecies you can get it from container:
$requestStack = $this->get('request_stack');
or just inject it into controller constructor (if your controller is a service).
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 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.
is there any way to preprocess controller data somehow. I'm going to take param from session, validate it and assign it as controller property and use it as $this->myVar inside actions of some controller or all of them if possible. Using controller's constructor gives me nothing, I couldn't access request and session data. Thanks!
UPD:
Thanks, jkucharovic, very good solution.
Also there is a bit dirtier solution, without injecting: setContainer() method, which has been called straight after $controller = new Controller();
use Symfony\Component\DependencyInjection\ContainerAwareInterface,
Symfony\Component\DependencyInjection\ContainerInterface;
class AppServiceController extends Controller {
private $my_property;
/**
* Used as constructor
*/
public function setContainer(ContainerInterface $container = null)
{
parent::setContainer($container);
$this->my_property = 'foo';
// your controller code
}
}
I'm not sure what you wan't to do is very usefull. A Controller instance will be created each time the controller is called. The session and request will be different each time you call the controller.
I think you should create a BaseController class extending Controller class with a shortcut method to access your MyVar value in session.
class BaseController extends Controller
{
public function getMyVar()
{
return $this->get('session')->get('MyVarSessionKey');
}
}
All your other Controller will extend from this BaseController.
To get the request, just use the shortcut method provided by Controller class, Controller::getRequest().
If you want to use services in __construct method, you have to inject that services first. Then you can use them before any other methods. For example:
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session;
public function __construct(Request $request, Session $session)
{
…
}