Pragmatism in the real world

Routing to a controller with Slim 2

In a couple of projects that I’ve written using Slim Framework 2, I’ve found it beneficial to organise my code into controllers with injected dependencies; probably because that’s how I’m used to working with ZF2.

To make this easier, I’ve written an extension to the main Slim class and packaged it into rka-slim-controller which will dynamically instantiate controllers for you for each route.

Defining routes is exactly the same as normal for a Slim controller, except that instead of a closure, you set a string containing the controller’s classname and method that you want to be called, separated by a colon:

$app = new \RKA\Slim();
$app->get('/hello/:name', 'App\IndexController:hello');
$app->map('/contact', 'App\ContactController:contact')->via('GET', 'POST');

Behind the scenes, this will create a closure for you that will lazily instantiate the controller class only if this route is matched. It will also try to retrieve the controller via Slim’s DI container which allows me to inject relevant dependencies into my controller class.

For example, you could group the functionality for authentication:

$app->get('/login', 'User\AuthController:login')->name('login');
$app->post('/login', 'User\AuthController:postLogin');
$app->get('/logout', 'User\AuthController:logout')->name('logout');

The controller needs to interact with a service class, say UserService, which is injected into the controller:

namespace User;

final class AuthController extends \RKA\AbstractController
{
    private $userService;

    public function __construct(UserService $userService)
    {
        $this->userService = $userService;
    }

    public function login()
    {
        // display login form
    }

    public function postLogin()
    {
        // authentication & redirect
    }

    public function logout()
    {
        // logout functionality
    }
}

In order to inject the service, we define a factory for the DI container and we’re done:

$app->container->singleton('User\AuthController', function ($container) {
    return new \User\AuthController($container['UserService']);
});

The nice thing about this approach is that I can group functionality that requires the same dependencies into a single class and be sure that I only instantiate the classes that I need in order to service the request.

6 thoughts on “Routing to a controller with Slim 2

  1. I'm not sure if I get it right. I understand route short form and passing a service. But setApp()/setRequest()/setResponse()? Isn't it enough to use setApp() since woth $app we have $app->request and $app->response? With $controller->setRequest($app->request) it means that there will always be request/response from Slim so why bother with setting it up in a separate method – only explanation I see is to make some operations on request/response right at the controller start. Witch is good :) And init()? What do you use it for, a place for common logic (for that controller) that shouldn't belong to proper controller methods like hello()?

    1. It's a personal preference thing. I separate Request and Response as I think of them as separate from the $app "God" object. Hence in my controller actions, I like to see $this->getRequest() rather than $this->getApp()->request.

      init() is useful for code that needs to run for every action in this controller.

  2. I have gone a step further.
    With this component you can integrate the ZF2 ServiceManager into Slim apps https://github.com/acelaya/slim-container-sm

    It makes Slim's container to be a ServiceManager under the hoods. If a Slim application has grown too much it can be of help.

    I'm not completly comfortable with the bootstraping steps, but it works. With this and your approach, controllers will be fetched from the ServiceManager.

Comments are closed.