# Внедрение зависимостей
По умолчанию в этом шаблоне приложения используется 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);
]);
По умолчанию в шаблоне приложения были созданы несколько фабрик:
- App\Infrastructure\Http\ApplicationFactory.
- App\Infrastructure\Http\ErrorHandlerMiddlewareFactory.
- App\Infrastructure\LoggerFactory.
# Использование контейнера при обработке маршрутов
/**
* @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));
}
}
Подробнее о маршрутизации читайте здесь.