2

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.

Luc Hamers
  • 167
  • 1
  • 10

2 Answers2

4

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)

danigore
  • 411
  • 1
  • 5
  • 8
0

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
}
msg
  • 7,863
  • 3
  • 14
  • 33
  • Thank you for the clarification. I do have the annotation for access control. I needed to pass the user object to a service, and it wasn't clear to me as I programmed this in Symfony 2.8, that it was already possible to do this by getting the token storage object directly in the service. Now, looking at this with the 4.3-documentation, it is clear that I do not need to pass it from the controller. – Luc Hamers Aug 10 '19 at 15:53
  • @LucHamers Since I didn't have more details I took a shot in the dark with one possible use case for this and that's why I suggested the annotations, but yes, instancing your dependency yourself would defeat the purpose of DI. Happy it worked out. – msg Aug 10 '19 at 17:33