Dependency Injection

Introduction

Route has the ability to use a PSR-11 dependency injection container to resolve any classes it needs to instantiate. Using a dependency injection container is no longer forced with Route, however, it is very much recommended.

It is recommended that if you have limited or no knowledge of dependency injection you should read about the concepts before you attempt to implement it. A good place to get started is with the Dependency Injection chapter of PHP The Right Way.

Using a Container

In these examples, we will be using league/container to demonstrate how to easily implement a dependency injection container with Route.

Consider that we have a controller class that needs a template renderer to load and render our HTML templates.

<?php declare(strict_types=1);

namespace Acme;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Laminas\Diactoros\Response;

class SomeController
{
    public function __construct(
        protected TemplateRenderer $templateRenderer
    ) {}

    public function __invoke(ServerRequestInterface $request): ResponseInterface
    {
        $body = $this->templateRenderer->render('some-template');
        $response = new Response;

        $response->getBody()->write($body);
        return $response->withStatus(200);
    }
}

We can now build a container, define our controller and set it on the strategy, when the route is matched, the controller will be resolved via the container with the template renderer passed to it.

<?php declare(strict_types=1);

$container = new League\Container\Container;

$container->add(Acme\SomeController::class)->addArgument(Acme\TemplateRenderer::class);
$container->add(Acme\TemplateRenderer::class);

$strategy = (new League\Route\Strategy\ApplicationStrategy)->setContainer($container);
$router = (new League\Route\Router)->setStrategy($strategy);

$router->map('GET', '/', Acme\SomeController::class);

Binding RouterInterface

Both League\Route\Router and League\Route\Cache\Router implement League\Route\RouterInterface. You can bind this interface in your container so that any service type-hinting against RouterInterface will receive the correct implementation:

<?php declare(strict_types=1);

use League\Route\RouterInterface;

$container = new League\Container\Container;

$container->add(RouterInterface::class, function () use ($container): RouterInterface {
    $strategy = (new League\Route\Strategy\ApplicationStrategy)->setContainer($container);
    $router   = (new League\Route\Router)->setStrategy($strategy);

    $router->map('GET', '/', Acme\SomeController::class);

    return $router;
});

This allows you to swap in the cached router for production without changing any code that depends on RouterInterface:

<?php declare(strict_types=1);

use League\Route\RouterInterface;

$container->add(RouterInterface::class, function () use ($container): RouterInterface {
    $builder = function (League\Route\Router $router) use ($container): League\Route\Router {
        $strategy = (new League\Route\Strategy\ApplicationStrategy)->setContainer($container);
        $router->setStrategy($strategy);
        $router->map('GET', '/', Acme\SomeController::class);
        return $router;
    };

    return new League\Route\Cache\Router(
        $builder,
        new League\Route\Cache\FileCache('/tmp/route.cache', 86400)
    );
});