2

I'm trying to build a simple module (to learn DI) to return the current user ID, and and apparently I'm missing something in the syntax.

Here's my code and the error it's producing:

use Drupal\Core\Session\AccountProxyInterface;
use Symfony\Component\HttpFoundation\Response;

 class LSWController {
  /**
   * @var AccountProxy
   */ 
     protected $currentUser;

     public function __construct(AccountProxyInterface $currentUser) {
         $this->currentUser = $currentUser;
     }

     public function say() {
         return new Response("Hello");
        //return new Response($this->currentUser->id());
     }
 }

and the error

ArgumentCountError: Too few arguments to function Drupal\lsw\Controller\LSWController::__construct(), 0 passed in C:\wamp64\www\testdrupal\core\lib\Drupal\Core\DependencyInjection\ClassResolver.php on line 31 and exactly 1 expected in Drupal\lsw\Controller\LSWController->__construct()

What am I missing? I'm including the AccountProxyInterface, etc. How come it's saying that 0 arguments are being passed?

2 Answers 2

5

Classes aren't naturally aware of the container, you have to implement them in such a way that they are. The most common approach for controllers is to extend Drupal\Core\Controller\ControllerBase, and override the create method, e.g.

use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Session\AccountProxyInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\DependencyInjection\ContainerInterface;

class LSWController extends ControllerBase {
  /**
   * @var AccountProxy
   */ 
   protected $currentUser;

   public static function create(ContainerInterface $container) {
     return new static($container->get('current_user'));
   }

   public function __construct(AccountProxyInterface $currentUser) {
     $this->currentUser = $currentUser;
   }

   public function say() {
     return new Response("Hello");
     //return new Response($this->currentUser->id());
   }
}

For you, though, this approach will be bittersweet...on the one hand you've learned how to inject services into a controller, which is great.

On the other hand, ControllerBase already has a $currentUser property, which resolves to the same object you'd be getting from the injected service. So in applying the common solution, you've removed your original need for DI, and made the create and constructor methods completely redundant ;)

As 4k4 very correctly points out in a comment, you don't have to extend ControllerBase; implementing ContainerInjectionInterface in your own controller class is enough. But the deliciousness of solving the problem with less code than when you started is surely too much to resist.

2
  • 1
    Even if you use ControllerBase you can still inject the service to the existing property if you want to have a proper DI. ControllerBase is then only like a fallback which loads the service statically if not already injected when initialized.
    – 4uk4
    Sep 7, 2018 at 6:34
  • Yep good point @4k4
    – Clive
    Sep 7, 2018 at 7:20
2

You need to inject the services through a public static function called create. In your case something like this:

   /**
   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
   *
   * @return static
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('current_user')
    );
  }

The services you load through the container should match those in your _construct function

2
  • 2
    Yes, create() is exactly what is missing for DI, but in a controller you need class LSWController implements ContainerInjectionInterface. Otherwise the class resolver won't find create().
    – 4uk4
    Sep 6, 2018 at 22:14
  • Yes you're right, I missed that in the code the OP posted :) Sep 6, 2018 at 22:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.