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.

Source: AKRABAT

By Rob