Edison
Edison

Reputation: 85

Slim Framework 3 - How to inject $logger to route controller

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?

Upvotes: 4

Views: 3533

Answers (2)

alexw
alexw

Reputation: 8668

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.

Upvotes: 4

Rob Allen
Rob Allen

Reputation: 12778

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.

Upvotes: 6

Related Questions