Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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-di

Connecting 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.

Thanks for reading!

Donate to the site — we work hard for you.