4

I am using Slim Framework 3. I want to inject $logger defined in dependencies.php into a Router Controller class. Below is what I do, is there a better way?

routes.php

$app->get('/test', function($request, $response, $args){
  $controller = new AccountController($this->get('logger'));
  return $controller->test($request, $response, $args);
});

AccountController

class AccountController{

    private $logger;
    function __construct($logger){
        $this->logger = $logger;
    }

    public function test($request, $response, $args){
        $this->logger->info('i am inside controller');
        return $response->withHeader('Content-Type', 'application/json')->write('test');
    }
}

In Slim Framework 3 documentation, the proper way of using a Route Controller should be:

$app->get('/test', 'AccountController:test');

But how do I inject $logger into AccountController when I choose to code my Route Controller in this more "elegant" way?

alexw
  • 8,468
  • 6
  • 54
  • 86
Edison
  • 85
  • 1
  • 7

2 Answers2

6

In terms of making your controller easier to test, you should inject the logger into the controller via the constructor.

AccountController looks like this:

class AccountController
{
    protected $logger;

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

    public function test($request, $response, $args){
        $this->logger->info('i am inside controller');
        return $response->withJson(['foo' => 'bar']);
    }
}

Set up in index.php is something like:

$container = $app->getContainer();
$container[Logger::class] = function ($c) {
    $logger = new \Monolog\Logger('logger');
    return $logger;
};
$container[AccountController::class] = function ($c) {
    $logger = $c->get(Logger::class);
    return new AccountController($logger);
};

$app->get('/test', 'AccountController:test');

Note that if you make the format route callable be a string of 'class name' colon 'method name', then Slim 3 will call the method for you after extracting the controller class from the DI container. If the class name is not a registered key with the container, then it will instantiate it and pass the container to the constructor.

Rob Allen
  • 12,643
  • 1
  • 40
  • 49
  • Hey, thanks for the answer. What happens if i have 10 controllers which need to use the logger? – silvedo Apr 29 '21 at 21:08
  • I create 10 factories as those controllers usually have other dependencies such as service classes or database mappers. – Rob Allen May 03 '21 at 08:37
4

According to the container resolution docs, you should be able to access your logger through the container, inside your controller:

AccountController

class AccountController
{
    protected $ci;

    //Constructor
    public function __construct(ContainerInterface $ci) 
    {
        $this->ci = $ci;
    }

    public function test($request, $response, $args)
    {
        $this->ci->get('logger')->info('i am inside controller');
        return $response->withHeader('Content-Type', 'application/json')->write('test');
    }
}

When you call $app->get('/test', 'AccountController:test');, Slim should automatically pass the container into AccountController's constructor.

That being said, this is more of a convenience feature than an example of great design. As Rob Allen explains in his answer, you can achieve better modularity, and thus more easily tested code (if you're using unit tests), by injecting the controllers into the application container, rather than injecting the container into each controller.

Take a look at his example Slim application. If you look at, for example AuthorController, you can see how with this design controller classes no longer depend on the magical container providing all the services. Instead, you explicitly state which services each controller will need in the constructor. This means you can more easily mock the individual dependencies in testing scenarios.

alexw
  • 8,468
  • 6
  • 54
  • 86
  • Thanks! How can I missed that piece of info! I got the container by: function __construct(Slim\Container $ci) – Edison Apr 09 '16 at 00:59
  • This is not correct way to inject logger to your controller. Because in this example, you inject container itselft. It is not correct way. You can check Rob's answer I think. http://stackoverflow.com/a/36517681/721600 – hkulekci Jul 17 '16 at 09:11
  • 1
    @hkulekci yes, I mentioned that in the rest of my answer. – alexw Jul 17 '16 at 15:38