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?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With