# Внедрение зависимостей

По умолчанию в этом шаблоне приложения используется devanych/di-container, это простой и легковесный контейнер с использованием автовайринга, реализующий PSR-11. Создание контейнера и внедрение зависимостей производится в файле config/container.php, а в других контекстах используется Psr\Container\ContainerInterface.

Вы можете легко изменить контейнер на вашу предпочтительную реализацию PSR-11. Для этого установите необходимую библиотеку и измените содержимое файла config/container.php.

# Пример использования с Devanych\Di

Установим необходимые зависимости для создания приложения самым простым способом.

$container = new Devanych\Di\Container([
    'debug' => true,
    HttpSoft\Basis\Application::class => function (Psr\Container\ContainerInterface $container) {
        return new HttpSoft\Basis\Application(
            $container->get(HttpSoft\Router\RouteCollector::class),
            $container->get(HttpSoft\Emitter\EmitterInterface::class),
            $container->get(HttpSoft\Runner\MiddlewarePipelineInterface::class),
            $container->get(HttpSoft\Runner\MiddlewareResolverInterface::class),
            new HttpSoft\Basis\Handler\NotFoundJsonHandler($container->get('debug'))
        );
    },
    HttpSoft\Router\RouteCollector::class => HttpSoft\Router\RouteCollector::class,
    HttpSoft\Emitter\EmitterInterface::class => fn() => new HttpSoft\Emitter\SapiEmitter(),
    HttpSoft\Runner\MiddlewarePipelineInterface::class => fn() => new HttpSoft\Runner\MiddlewarePipeline(),
    HttpSoft\Runner\MiddlewareResolverInterface::class => fn($c) => new HttpSoft\Runner\MiddlewareResolver($c);
]);

Обратите внимание, что для создания экземпляра HttpSoft\Router\RouteCollector будет использоваться автовайринг, так как в качестве значения указан просто класс. Контейнер переберет все зависимости конструктора и попытается их разрешить. Если автовайринг не нужен, то для лучшей производительности просто оберните создание объекта анонимной функцией.

HttpSoft\Router\RouteCollector::class => fn() => new HttpSoft\Router\RouteCollector()

Для удобства можно создавать и использовать фабрики, реализующие Devanych\Di\FactoryInterface.

use Devanych\Di\FactoryInterface;
use HttpSoft\Basis\Application;
use HttpSoft\Basis\Handler\NotFoundJsonHandler;
use HttpSoft\Emitter\EmitterInterface;
use HttpSoft\Router\RouteCollector;
use HttpSoft\Runner\MiddlewarePipelineInterface;
use HttpSoft\Runner\MiddlewareResolverInterface;
use Psr\Container\ContainerInterface;

final class ApplicationFactory implements FactoryInterface
{
    public function create(ContainerInterface $container): Application
    {
        return new Application(
            $container->get(RouteCollector::class),
            $container->get(EmitterInterface::class),
            $container->get(MiddlewarePipelineInterface::class),
            $container->get(MiddlewareResolverInterface::class),
            new NotFoundJsonHandler($container->get('config')['debug'])
        );
    }
}

Фабрика, как значение зависимости, при создании всегда будет возвращать не собственный объект, а объект, создаваемый методом create().

// With autowiring
HttpSoft\Basis\Application::class => ApplicationFactory::class,
// Without autowiring
HttpSoft\Basis\Application::class => fn() => new ApplicationFactory(),

Сложную логику создания объектов лучше выносить в фабрики. Итоговый код установки зависимостей стал гораздо меньше.

$container = new Devanych\Di\Container([
    'debug' => true,
    HttpSoft\Basis\Application::class => fn() => new ApplicationFactory(),
    HttpSoft\Router\RouteCollector::class => fn() => new HttpSoft\Router\RouteCollector(),
    HttpSoft\Emitter\EmitterInterface::class => fn() => new HttpSoft\Emitter\SapiEmitter(),
    HttpSoft\Runner\MiddlewarePipelineInterface::class => fn() => new HttpSoft\Runner\MiddlewarePipeline(),
    HttpSoft\Runner\MiddlewareResolverInterface::class => fn($c) => new HttpSoft\Runner\MiddlewareResolver($c);
]);

По умолчанию в шаблоне приложения были созданы несколько фабрик:

# Использование контейнера при обработке маршрутов

/**
 * @var Psr\Container\ContainerInterface $container
 */

// Создание объекта приложения.
$app = $container->get(HttpSoft\Basis\Application::class);

// Добавление маршрутов.
$app->get('home', '/', HomeAction::class);
$app->get('list', '/page/{id}', PageAction::class)->tokens(['id' =>  '\d+']);

// Запуск приложения.
$app->run(HttpSoft\ServerRequest\ServerRequestCreator::create());

В примере добавления маршрутов в качестве обработчиков/экшенов используются вымышленные имена классов (HomeAction и PageAction). Такой подход позволяет организовывать обработку каждого маршрута в отдельном классе. Эти классы реализуют Psr\Http\Server\RequestHandlerInterface, подробнее об обработчиках смотрите в HttpSoft\Runner\MiddlewareResolver.

use HttpSoft\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

final class HomeAction implements RequestHandlerInterface
{
    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        return new JsonResponse(['name' => 'HomeAction']);
    }
}

Объекты обработчиков/экшенов будут созданы через контейнер, поэтому они могут принимать зависимости в конструкторе.

use HttpSoft\Basis\Exception\NotFoundHttpException;
use HttpSoft\Basis\Response\PrepareJsonDataTrait;
use HttpSoft\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

final class PageAction implements RequestHandlerInterface
{
    use PrepareJsonDataTrait;

    private PageRepository $pages;

    public function __construct(PageRepository $pages)
    {
        $this->pages = $pages;
    }

    public function handle(ServerRequestInterface $request): ResponseInterface
    {
        /** @var JsonSerializable|null $page */
        if (!$page = $this->pages->findById((int) $request->getAttribute('id'))) {
            throw new NotFoundHttpException();
        }

        return new JsonResponse($this->prepareJsonData($page));
    }
}

Подробнее о маршрутизации читайте здесь.