The dependency container
Actions need other objects: a class that works with posts, a database connection, a template engine. Such objects are called dependencies. A dependency container is an object that knows how to create them and hand them to wherever they are needed. Slim has no container — the PHP-DI package is added.
Installation
composer require php-di/php-diConnecting it
The container is built in config/bootstrap.php and passed to the application factory:
// config/bootstrap.php
use DI\ContainerBuilder;
use Slim\Factory\AppFactory;
require __DIR__ . '/../vendor/autoload.php';
$builder = new ContainerBuilder();
$builder->addDefinitions(__DIR__ . '/container.php');
$container = $builder->build();
$app = AppFactory::createFromContainer($container);
(require __DIR__ . '/routes.php')($app);
return $app;For now the config/container.php file returns an empty list of definitions — create it by hand:
// config/container.php
return [];The pages work as before, but action objects are now created by the container.
Autowiring
Create a class that returns posts. There is no database yet, so the posts live in an array:
// src/Repository/PostRepository.php
namespace App\Repository;
final class PostRepository
{
public function all(): array
{
return [
['id' => 1, 'title' => 'First post'],
['id' => 2, 'title' => 'Second post'],
];
}
}The action needs it — so it is listed in the constructor:
// src/Action/ListPostsAction.php
namespace App\Action;
use App\Repository\PostRepository;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
final class ListPostsAction
{
public function __construct(private PostRepository $posts) {}
public function __invoke(Request $request, Response $response): Response
{
foreach ($this->posts->all() as $post) {
$response->getBody()->write('<p>' . $post['title'] . '</p>');
}
return $response;
}
}// config/routes.php
$app->get('/posts', ListPostsAction::class)->setName('posts.index');The /posts address shows two titles. Nowhere does the code say new PostRepository(): the container sees the parameter type in the constructor and creates the object itself. This is called autowiring.
When a definition is needed
Autowiring cannot cope when the constructor asks for an interface or a plain value — a string, an array. Then a definition is added to config/container.php: the key is a name, the value is a function that creates the object.
// config/container.php
use Psr\Http\Message\ResponseFactoryInterface;
use Slim\Psr7\Factory\ResponseFactory;
return [
ResponseFactoryInterface::class => fn () => new ResponseFactory(),
];Now any class that asks for ResponseFactoryInterface in its constructor receives a ResponseFactory object. This entry will be useful in the middleware lessons. The function runs once, on first access; after that the container returns the same object.
Common mistake
Do not pass the container itself into your classes in order to call $container->get(...) inside: the constructor no longer shows what the class really needs, and the class becomes hard to test. List the concrete dependencies in the constructor — autowiring does the rest.