Use constructor in Symfony2 controller - symfony

I'd like to use a constructor in my Symfony2 controller, but I have this error:
ContextErrorException: Catchable Fatal Error: Argument 1 passed to Acme\ApplicationBundle\Controller\CalendarActivityController::__construct() must implement interface Symfony\Component\DependencyInjection\ContainerInterface, none given, called in /Users/root/Documents/projects/Acme/Symfony/app/cache/dev/classes.php on line 2374 and defined in /Users/root/Documents/projects/Test/Symfony/src/Acme/ApplicationBundle/Controller/CalendarActivityController.php line 16
My constructor:
public function __construct(Container $container) {
$this->container = $container;
}
I must use a constructor because sometimes I'm using my controller as a service, and in this case container is not defined.

It is possible to inject services into your controller if your controller is registered as a service itself. Read more about it in the official docs: http://symfony.com/doc/current/cookbook/controller/service.html
However, let me point out, that there's not much value in injecting the Container. It's much easier to extend the Symfony\Component\DependencyInjection\ContainerAware. Symfony will inject the container for you automatically (since ContainerAware implements the ContainerAwareInterface).
Register your controller as a service if you want to inject specific services.
Use ContainerAware otherwise.

you have to call container from dependncy injection use USE
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
private $container;
public function __construct(Container $container) {
$this->container = $container;
}

Related

Symfony 4.4 service autowired in controller's constructor doesn't get call from service definition (setContainer)

Foreword: please disregard injecting the whole container and other unclean things, I just wanted to show not-working example, this is before refactor.
I have a service defined in YAML:
app.service.my_service:
class: App\Services\MyService
public: true
calls:
- [setContainer, ['#service_container']]
part of my service:
class MyService implements ContainerAwareInterface
{
/** #var ContainerInterface */
private $container;
/** #var EntityManagerInterface */
private $em;
public function setContainer(?ContainerInterface $container = null)
{
$this->container = $container;
$this->em = $container->get('doctrine')->getManager();
}
Then I have a controller, which have that service autowired in constructor, and not instantiaded from container:
class MyController
{
/**
* #var MyService
*/
private $my_service;
function __construct(MyService $my_service) {
$this->my_service = $my_service;
}
While it actually autowires service itself, it completely ignores the setContainer call, so I end up with empty container.
I wanted to avoid calling $this->get('app.service.my_service') everywhere and I can't simply call it once in controller's contructor, or call setContainer in the constructor on autowired service, because at that time container is empty.
Any chances I can do it in a clean way?
Since you already know injecting the container is a bad idea then I guess I'll spare you the lecture. I have not encountered this particular issue but you can try using the little known '#required' container directive and see if it helps.
/** #required */
public function setContainer(ContainerInterface $container)
{
$this->container = $container;
$this->em = $container->get('doctrine.orm.default_entity_manager);
}
This is more of a guess than an answer but it is easy to try.
Update: It worked. Cool.
I just wanted to add that, in most cases, dependencies should be injected into the constructor. This avoids having partially initialized services. On the other hand, as long as you are generating your services via the container then it is not really a problem.
I find it to be very useful in traits. For example:
trait RouterTrait
{
private RouterInterface $router;
/** #required */
public function setRouter(RouterInterface $router)
{
$this->router = isset($this->router) ? $this->router: $router;
}
// These are just copied from AbstractController
protected function generateUrl(
return $this->router->generate(...
protected function redirectToRoute(
#
class SomeService
use RouterTrait;
public function someMethod()
$this->generateUrl('''
So if a service needs to do some routing then they just need to use the router trait and the router is automatically injected. Saves adding boilerplate code to the service's constructor. I do put a guard around setRouter so it can only be effectively called once which eliminates another concern about using setters for dependencies.
Update #2:
The problem with the original code is that the MyService was defined with a service is of app.service.my_service and not the class name. Autowire actually generated a second MyService and injected it since autowire looks for service ids that match the class name. Changing the service definition to:
App\Services\MyService:
public: true
calls:
- [setContainer, ['#service_container']]
Would have also worked. Or explicitly injecting app.service.my_service into the controller.

getUser() method doesn't work in the controllers constructor

I want to fetch the user object in a controllers constructur in a Symfony 4.3.2 project. According to the docs on https://symfony.com/doc/4.0/security.html#retrieving-the-user-object, I just need to call $this->getUser(). And yes, this works in action methods.
BUT: trying to get the user in the constructor doesn't work, because the container will NOT be initialized here and the getUser method throws an exception "Call to a member function has() on null": the container is null at this point in time.
This works:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class TestController extends AbstractController
{
public function indexAction()
{
dump($this->getUser());
}
}
This doesn't:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class TestController extends AbstractController
{
public function __contruct()
{
dump($this->getUser());
}
public function indexAction()
{
}
}
And when I inject the container manually, then all is fine too:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class TestController extends AbstractController
{
public function __construct(ContainerInterface $container)
{
$this->container = $container;
dump($this->getUser());
}
public function indexAction()
{
}
}
btw, this is the getUser method in AbstractController:
protected function getUser()
{
if (!$this->container->has('security.token_storage')) {
throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".');
}
......
Is this a bug, that the container is not initialized in the constructor or is it a feature, that you have to initialize this by hand when you need the user in the constructor?
Edit: using the way shown in https://symfony.com/blog/new-in-symfony-3-2-user-value-resolver-for-controllers does work in actions, but it doesn't work in the constructor:
....
private $user;
public function __construct(UserInterface $user)
{
$this->user = $user;
}
produces the following error message: Cannot autowire service "App\Controller\TestController": argument "$user" of method "__construct()" references interface "Symfony\Component\Security\Core\User\UserInterface" but no such service exists. Did you create a class that implements this interface?. And that is where I would like to set the user object.
NEVER USE $security->getUser() or $this->getUser() in constructor!!
auth may not be complete yet. (In Service Instead, store the entire Security object. :
symfony.com/doc/security.html#a-fetching-the-user-object
... and you can use $this->getUser() in any Controller what extended with the AbstractController. (Just not in the constructor)
The container gets set by the ControllerResolver after the Controller has been instanced by calling the setContainer method that you mention. Thus, when the constructor is called the container is not available by design.
You might have a use case, but I don't see why you want to do this since in your controller methods you will have to access the $user property and it'll just save you typing get(). You can inject the whole container as shown in your sample or you can inject just the Security service.
use Symfony\Component\Security\Core\Security;
class TestController extends AbstractController
{
private $user;
public function __construct(Security $security)
{
$this->user = $security->getUser();
}
public function indexAction()
{
$user = $this->user; // Just saved you typing five characters
// At this point the container is available
}
}
I'm not actually setting the security service because it'll become available later through the container.
If you want to do this to enforce access control for the whole class you can use the Security annotations:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
/**
* #IsGranted('ROLE_USER')
*/
class TestController extends AbstractController
{
// Only authenticated user will be able to access this methods
}

Symfony Dependency injection with EntityMenager

I had to call controller in service. In that manner I had created connstruct function and add parameter EntityMenager
class UsersController extends Controller
{
private $em;
public function __construct(EntityManager $em) {
$this->em = $em;
}
and defined service for that controller
services:
user_controller:
class: AppBundle\Controller\UsersController
arguments:
['#doctrine.orm.entity_manager']
when I call that service
$usersContainer = $this->get('user_controller');
in other controllers everything is fine.
But when I call self controller I get error
Catchable Fatal Error:
Argument 1 passed to AppBundle\Controller\UsersController::__construct()
must be an instance of Doctrine\ORM\EntityManager, none given,
Where I making mistake?
I just do ugly hack and solve problem at this moment.
I had removed __construc class and add EntityManager as needed parameter to function that needs EntityManager class.
public function getLoggedUserData($loggedUserId, $entityManager)
{
$user = $entityManager->getRepository('...')
$newRequests = $entityManager->getRepository('...')
You can only define a Controller as service, if it is a simple class (not extending Controller).
See : http://symfony.com/doc/current/cookbook/controller/service.html
And particularly :
http://symfony.com/doc/current/cookbook/controller/service.html#alternatives-to-base-controller-methods

Inject Container in Entity Repository

I want to get Current locale in my Repository.That's why I am injecting the Container into my Repository but I am getting error that I am unable to figure it out.
This is my service.yml code
survey.repository.container_aware:
class: Demo\SurveyBundle\Repository\SurveyRepository
calls:
- [ setContainer, [ #service_container ] ]
and this is my repository class code
.......
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
.......
protected $container;
public function __construct(Container $container) {
$this->container = $container;
}
After that I am getting below error
ContextErrorException: Catchable Fatal Error: Argument 1 passed to
Demo\SurveyBundle\Entity\SurveyRepository::__construct() must implement
interface Symfony\Component\DependencyInjection\ContainerInterface, instance of
Doctrine\ORM\EntityManager given
What I am missing in my construct or in service?
You are passing the container with the Setter Injection (in the yml) but you define it in the constructor class.
BTW the entity manager already have a constructor class with arguments so don't take the Constructor Injection and simply change your method in the class as:
public function setContainer(Container $container) {
$this->container = $container;
}
You actually have another major issue here. From the error message it's obvious that you are trying to access your doctrine repository using the entity manager. Something like:
$repo = $em->getRepository('whatever');
The service container code is never being used and it really does not matter what you do, you still won't get your container injected. Creating a repository as a service requires using the entity manager as a factory and takes some additional lines in your services.yml file.
Something like:
# services.yml
cerad_person.person_repository.doctrine:
class: Cerad\Bundle\PersonBundle\Entity\PersonRepository
factory_service: 'doctrine.orm.default_entity_manager'
factory_method: 'getRepository'
arguments:
- 'Cerad\Bundle\PersonBundle\Entity\Person'
calls:
- [ setContainer, [#container] ]
// controller
$personRepo = $this->get('cerad_person.person_repository.doctrine');
That will give you a repository with the container injected.
#devilciuos - %locale% will only give you the default locale and not whatever is passed as _locale in the request. Unfortunately it seems to take a listener to access the request local via a service: https://github.com/symfony/symfony/issues/5486
You're not passing the container to the constructor, but to setContainer. So you shouuld declare a public method setContainer in SurveyRepository:
Demo/SurveyBundle/Entity/SurveyRepository.php
protected $container;
public function setContainer(Container $container) {
$this->container = $container;
}
or pass the container to the constructor:
DemoSurveyBundle/Resources/Config/services.yml
survey.repository.container_aware:
class: Demo\SurveyBundle\Repository\SurveyRepository
arguments: [#service_container]
Edit:
By the way, if you only need the locale, wouldn't be enough to pass the %locale% parameter instead of the whole container?
survey.repository.container_aware:
class: Demo\SurveyBundle\Repository\SurveyRepository
calls:
- [ setLocale, [ %locale%] ]
protected $locale;
public function setLocale($locale) {
$this->locale = $locale;
}

How do I get services (dependencies) in a custom class

In the controller, I could do
$this->get('service.name')
But in a custom class, how can I do that?
Define your custom class as a service, and then inject dependencies into it.
Ex:
// services.yml
services:
my.custom.service.id:
class: My\Custom\Class
arguments:
- #service.name
- #doctrine.orm.entity_manager
Your custom class' constructor would then get those services as arguments.
Be sure to read up on the Service Container in the official docs. It goes over all this in great detail.
You were on the right track with ContainerAware.
$this->get('id') is actually a shortcut to $this->container->get('id'). And getting container into your class is as simple as implementing ContainerAwareInterface - putting this snippet into your class:
public function setContainer(\Symfony\Component\DependencyInjection\ContainerInterface $container = null)
{
$this->container = $container;
}
If you do not know the full list of dependencies that you need at the moment when the service is created, you can pass the container as the argument http://symfony.com/doc/current/book/service_container.html#using-the-expression-language
services:
service_name:
class: AppBundle\Class
arguments: ['#=container']
Accessing the service container in a custom class (not in a service defined class)
This is not best practice to do, but it works. If your custom class is not set to be a service then you can access the service container using global variable $kernel:
class Helper {
private $container;
/**
* Constructor assigns service container to private container.
*/
public function __construct() {
global $kernel;
$this->container = $kernel->getContainer();
}
function doSOmething() {
$someService = $this->container->get('service.name');
// do something with someService ...
}
}
OK I suppose #Arms answer is a possible solution, I found by looking at the source to Controller, I could extend ContainerAware

Resources