Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 930
Edison Avatar asked Dec 25 '22 07:12

Edison


2 Answers

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.

like image 67
Rob Allen Avatar answered Jan 14 '23 06:01

Rob Allen


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.

like image 37
alexw Avatar answered Jan 14 '23 06:01

alexw