Search code examples
symfonysymfony-4.3

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.


Solution

  • 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
    }