Dependency Injection
As soon as an app needs a database connection, a logger, or a mailer, a question comes up: where should that object be created, and how does every route handler that needs it get access to the same instance? A dependency injection (DI) container answers this — it's a central place that knows how to create and hand out shared objects, called services, instead of every piece of code creating its own fresh copy with new.
Slim and PSR-11
Slim doesn't force a specific container on you. It works with any container that implements PSR-11 — the standard PHP interface for containers (get() and has() methods). A very common choice in the Slim world is PHP-DI, which is easy to set up and adds some convenient extra features on top of the plain PSR-11 interface.
Registering a Service
A container is typically configured with a definitions array: keys are service names, and values are functions that know how to build each one. Here's a container holding a single 'db' service, built with PHP-DI and handed to the app.
<?php
require __DIR__ . '/../vendor/autoload.php';
$containerBuilder = new \DI\ContainerBuilder();
$containerBuilder->addDefinitions([
'db' => function () {
return new PDO('mysql:host=localhost;dbname=my_app', 'root', '');
},
]);
$container = $containerBuilder->build();
\Slim\Factory\AppFactory::setContainer($container);
$app = \Slim\Factory\AppFactory::create();
Retrieving a Service in a Route Handler
Once the container is wired up, a route handler can ask for the 'db' service by name instead of building its own PDO object. When the route closure is bound to the container, $this->get() resolves a service; otherwise, the container is available through the app.
<?php
$app->get('/users', function ($request, $response) {
$db = $this->get('db');
$statement = $db->query('SELECT id, name FROM users');
$users = $statement->fetchAll(PDO::FETCH_ASSOC);
$response->getBody()->write(json_encode($users));
return $response->withHeader('Content-Type', 'application/json');
});
Every route that needs the database asks the container for 'db' the same way — the connection details live in exactly one place, the container definition.
Why This Matters
| Problem it solves | How DI helps |
|---|---|
| Scattered new PDO(...) calls | Connection details live in one container definition instead of copy-pasted everywhere |
| Hard to test | In tests, a fake/mock service can be registered in place of the real one, without touching route code |
| Tight coupling | Route handlers depend on a service name, not a concrete class they must construct themselves |
Dependency injection is a deep topic with plenty of nuance — auto-wiring, factories, scopes — but the core idea above (register once, retrieve by name, share the instance) is enough to start building real Slim apps that stay easy to change and test.