Part 4. Converting Route Closures to Controller Classes
Route Control
Right now we have all of our routing and controller logic in one procedural file that we are treating like a service definition. This is not good practice for anything more than a single-page site and will become unruly fairly quickly.
Having the route definitions here makes sense but we need to break the logic and functionality out. We will be putting our logic into controller classes and then loading those classes as Services.
Let’s set up our structure.
Create the directory src/App/Controller
. This will hold our controller classes. In this directory create a file called DefaultController.php
with a matching class inside.
namespace App\Controller; class DefaultController { public function __construct() { } public function index() { } }
Now let’s create a file, configuration/services/controllers.php
, to put our service definitions into. Add the following code for now.
/** @var \Interop\Container\ContainerInterface $container */ $container = $app->getContainer(); $container['DefaultController'] = function($container){ return new \App\Controller\DefaultController(); };
Now let’s take a look at our default route. You’ll notice that it requires some information in order to function. Namely the logger and the renderer. These are themselves services that are defined in services.php. Here is where Dependency Injection comes into play. Our service container already knows how to create these objects when needed, so let’s let it do the work for us and just pass those objects into the controller’s constructor.
Note: the logger would normally be pushed down to the Service Level of our application. This will happen later when we get that far, but for now, we’ll just move it here as it is part of the current route closure.
In the DefaultController class, add properties to hold these two objects. Then assign them to the constructor as passed arguments.
use Psr\Log\LoggerInterface; class DefaultController { private $logger; private $renderer; public function __construct(LoggerInterface $logger, $renderer) { $this->logger = $logger; $this->renderer = $renderer; }
And in the controllers.php modify our DefaultController definition to pass the service calls into the controller.
$container['DefaultController'] = function($container){ return new \App\Controller\DefaultController( $container->get('logger'), $container->get('renderer') ); };
We now have a default controller that knows how to instantiate itself!
Let’s add our functionality to the Default Controller’s index class. I should mention here that all controller methods have three parameters by default.
$request
, which is a Request object. Slim uses PSR-7 so this implements theRequestInterface
.$response
, which is theResponse
object and implements theResponseInterface
.$args
, an associative array of placeholders which represents the query parameters.
First, include the interfaces in the file header.
use Psr\Http\Message\RequestInterface; use Psr\Http\Message\ResponseInterface;
Then add our default controller method, type hinting the parameters, and add the code from the route’s closure. Your index function should look like this.
public function index(RequestInterface $request, ResponseInterface $response, $args) { // Sample log message $this->logger->info("Slim-Skeleton '/' route"); // Render index view return $this->renderer->render($response, 'index.phtml', $args); }
Now that everything is set up let’s fix our route definition to use this new setup.
Instead of containing a closure function as its callback, our route will take the name of the controller service created above. The Pimple container works as a Service Locator here to find the DefaultController
service and index
methods. It then passes it back to the Router. The request, response, and passed arguments array will be automatically injected into the index method by our container.
$app->get('/[{name}]', 'DefaultController:index');
Finished.
That’s it, the route will now call our controller classes’ index method and automatically pass it everything it needs to render our output. All of our routes can now be defined, and cleanly read, in the routes.php configuration file. While all of the control functionality will be kept in controller classes that correspond to the specific aspects of our application. Separating our concerns.
Code.
See the final code for this article on github. 004 Converting Routes to Controller Classes