Контейнер зависимостей
Действиям нужны другие объекты: класс для работы со статьями, подключение к базе, шаблонизатор. Такие объекты называют зависимостями. Контейнер зависимостей — объект, который умеет создавать их и передавать туда, где они нужны. В Slim контейнера нет — подключают пакет PHP-DI.
Установка
composer require php-di/php-diПодключение
Контейнер создают в config/bootstrap.php и передают фабрике приложения:
// 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;Файл config/container.php пока возвращает пустой список описаний — создайте его вручную:
// config/container.php
return [];Страницы работают как раньше, но теперь объекты действий создаёт контейнер.
Автосвязывание
Создайте класс, который отдаёт статьи. Пока базы нет, статьи хранятся в массиве:
// src/Repository/PostRepository.php
namespace App\Repository;
final class PostRepository
{
public function all(): array
{
return [
['id' => 1, 'title' => 'Первая статья'],
['id' => 2, 'title' => 'Вторая статья'],
];
}
}Действию он нужен — значит, его перечисляют в конструкторе:
// 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');По адресу /posts появятся два заголовка. Нигде не написано new PostRepository(): контейнер видит тип параметра в конструкторе и сам создаёт нужный объект. Это называется автосвязыванием (autowiring).
Когда нужно описание
Автосвязывание не справится, если в конструкторе указан интерфейс или простое значение — строка, массив. Тогда в config/container.php добавляют описание: ключ — имя, значение — функция, создающая объект.
// config/container.php
use Psr\Http\Message\ResponseFactoryInterface;
use Slim\Psr7\Factory\ResponseFactory;
return [
ResponseFactoryInterface::class => fn () => new ResponseFactory(),
];Теперь любой класс, который просит в конструкторе ResponseFactoryInterface, получит объект ResponseFactory. Эта запись пригодится в уроках о посредниках. Функция выполняется один раз, при первом обращении; дальше контейнер отдаёт тот же объект.
Частая ошибка
Не передавайте в свои классы сам контейнер, чтобы вызывать внутри $container->get(...): по конструктору станет не видно, что классу нужно на самом деле, и проверить его тестом будет трудно. Перечисляйте в конструкторе конкретные зависимости — остальное сделает автосвязывание.