Pragmatism in the real world

DI Factories for Slim controllers

When using classes for route actions in Slim 3, I recommend using a single class for each route. However you can use a single class for multiple routes.

To register a class method to a route you pass a string as the route callable where the class name is separate from method by a colon like this:

$app->get('/list', 'MyController:listAction');

Slim will retrieve MyController from the DI container and then call the listAction method using the usual signature:

function (Request $request, Response $response, $args = []) : Response;

If you don’t specify a method, then Slim will see if it treat the class as a callable, so you can implement __invoke() and then register the route like this:

$app->get('/list', 'MyController');

and Slim will call MyController::__invoke() for you.

Writing a DI factory for your class

Usually, your controller action will need some dependencies in order to work, such as access to a service layer class, or ORM’s entity manager.

To handle this, you should inject the dependency in your controller’s constructor by writing a factory for the DI container. This sounds scary and complicated, but a factory is just another way of saying “a function that instantiates an object”. This is the simplest DI container factory we can write for MyController:

// Retrieve container instance
$container = $app->getContainer();

// Register MyController
$container['MyController'] = function ($c) {
    return new MyController();
};

The closure is the factory and as you can see, it simply returns an new instance of MyController. It is registered with Slim’s default DI container by assigning the closure to an array key (['MyController']) and it is vital that the string you use here is the same as the string you use before the colon in the route configuration ('MyController:list'.

Injecting the dependencies

To inject the dependencies, we register them with the DI container too as factories and then retrieve them in our controller factory.

Firstly, register a dependency:

$container['DatabaseService'] = function ($c) {
    return new DatabaseService();
};

Now we can use this in our controller factory. To do this note that the factory closure has a parameter, $c, which is the DI container itself. This means we can retrieve anything that’s registered with the DI container by using the get() method.

Hence we update our controller factory like this:

$container['MyController'] = function ($c) {
    $dbService = $c->get('DatabaseService');
    return new MyController($dbService);
};

The MyController constructor now receives our dependency and can store it to a class property ready for use in the route action method like this:

final class MyController
{
    private $dbService;
    public function __construct($dbService)
    {
        $this->dbService = $dbService;
    }

    public function listAction($request, $response, $args)
    {
        $dataArray = $this->dbService->fetchData();
        return $response->withJson($dataArray);
    }
}

There are numerous advantages to doing this. The main one for me is that there are no surprise dependencies any more. You can look at the constructor and know exactly which classes this class needs to do its job. You can also test it more easily which is beneficial!

I prefer to use one class for each route action as I can ensure that the dependencies that are injected are the correct ones for this action. When using multiple action methods in a controller class, you start needing to inject classes that are only used for just one or two of the actions and this is inefficient, especially if those dependencies are relatively expensive to construct. There are ways around this if you use a more powerful DI container such as Zend-ServiceManager though.

10 thoughts on “DI Factories for Slim controllers

  1. Hi,

    Could you please explain why don't you use ContainerInterface to create your controller and get access to container from your listAction ? In this case you can access dbService from main container and you don't need to do this:

    $container['MyController'] = function ($c) {
        $dbService = $c->get('DatabaseService');
        return new MyController($dbService);
    };
    

    Please look below:

    $container['DatabaseService'] = function ($c) {
        return new DatabaseService();
    };
    
    class MyController {
       protected $ci;
       //Constructor
    //That is from official docs:
       public function __construct(ContainerInterface $ci) {
           $this->ci = $ci;
       }
       
       public function listAction($request, $response, $args) {
            //your code
            //to access items in the container... $this->ci->get('');
    $this->ci->get('DatabaseService')->SomeActionWithDatabase();
    
       }
    }
    
    
    
    				
    1. Xia,

      I am using the Container to create the controller as that's how Slim's dispatch system works.

      You are asking why I don't let Slim pass in the DI container to the constructor of my controller. Instead I create my own DI factory closure which passes in the correct dependency (the $dbService in this case) to the controller.

      I covered this at the end of the article:

      There are numerous advantages to doing this. The main one for me is that there are no surprise dependencies any more. You can look at the constructor and know exactly which classes this class needs to do its job. You can also test it more easily which is beneficial!

      Taking a DI container into the constructor is a design pattern called Service Location. This means that the class has access to every service in the DI container and results in this class having hidden dependencies which is problematic. See the Wikipedia article about it.

      Also, Matthew Weier O'Phinney wrote an excellent article about this that explains it well: On Deprecating ServiceLocatorAware.

  2. Hi Rob,

    class methods in routes are great and easy to set up. But what would you do in situation when you have multiple controllers injected in container, and those controllers constructors need few other dependencies?

    It takes a while to write all that code. So is it considered as a bad practice to inject the whole container to controllers when defining dependencies, or use xias method to do the same thing by using type hinting?

    Very often you need views, csrf etc. in all controllers.

    I've done some tests by using:

    [code]
    $container['HomeController] = function ($c) {
    return new \App\Controllers\HomeController($c);
    }
    [/code]

    Also all controllers extend basecontroller, which takes care of settings container as a property. So then we can use i.e. views in all controllers by calling $this->container->view or even $this->view if basecontroller uses magic method.

    Things would come even easier by using xias method. Then there's no need to pass container to every controller when defining dependencies.

    What's your opinion about this when there's multiple controllers? And is there any difference if container is passed to constructor manually vs. type hinted?

    p.s. Also I've been reading about PHP-DI, but coudn't find any reasonable resource about performance with slim (memory usage + exec time). Do you have any performance related experience with Slim and PHP-DI together?

    1. RS,

      Slim will automatically inject the container into the controller's constructor if you don't create a DI factory for it.

      Personally, I don't mind creating factories. I'm a fan of Zend-ServiceManager which supports a mechanism called Abstract Factories which allows for one factory to create multiple controllers. I very much dislike using a container object in an action method within a controller class.

      I've never used PHP-DI.

      1. Perfect, that all makes sense. Somehow I skipped that part in Slim's User Guide.

        Thanks for your answer!

  3. Great article and this applies heavily to my project I'm working on. I do have a question, however. You mentioned you would suggest setting up the dependencies like this:

    $c = $app->getContainer();
    
    $c['DBService'] = function() {
    	return new DBService;
    };
    
    $c['Controller'] = function($c) {
    	$dbService = $c->get('DBService');
    	return new Controller($dbService);
    };
    

    But wouldn't this work as well or am I missing something? Maybe it just comes down to preference?

    $dbService = new DBService;
    
    $c = $app->getContainer();
    
    $c['Controller'] = function($c) use ($dbService) {
    	return new Controller($dbService);
    };
    
    1. The main difference is that in your second code-sample, you always instantiate a `DBService` object. If you then never need to use that object, you've wasted time, memory and resources for no reason.

  4. Thanks Rob for this. I dont know english so I must read more times again for understand all.

Comments are closed.