Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Контейнер зависимостей

Действиям нужны другие объекты: класс для работы со статьями, подключение к базе, шаблонизатор. Такие объекты называют зависимостями. Контейнер зависимостей — объект, который умеет создавать их и передавать туда, где они нужны. В 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(...): по конструктору станет не видно, что классу нужно на самом деле, и проверить его тестом будет трудно. Перечисляйте в конструкторе конкретные зависимости — остальное сделает автосвязывание.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.