Документация/Контейнер/Устройство контейнера

Контейнер

Контейнер во фреймворке HLEB2 представляет собой сборник так называемых сервисов, которые можно из него получить, а также добавить в контейнер.
Сервисы - логически самодостаточные структуры по предназначению.

Во фреймворке HLEB2 инициализация сервисов в контейнере избавлена от лишней абстракции.
Они инициализируются не фреймворком из конфигурации, как это обычно реализовано, а в специальном классе App\Bootstrap\BaseContainer, доступному для редактирования разработчиком, использующим фреймворк. (По большей части вам понадобится класс App\Bootstrap\ContainerFactory, так как там задаются сервисы в качестве singleton.)
Все файлы этих классов находятся в папке /app/Bootstrap/ проекта.

Благодаря такому устройству, в контейнер может быть добавлено достаточно большое количество сервисов без значимого ущерба для производительности.


#Класс BaseContainer

Представляет собой тот самый контейнер, который будет использоваться для получения сервисов.

Если необходим сервис как новый экземпляр класса при каждом запросе из контейнера, то его нужно указать здесь в выражении match().

<?php
// File /app/Bootstrap/BaseContainer.php

namespace App\Bootstrap;

use 
Hleb\Constructor\Containers\CoreContainer;

final class 
BaseContainer extends CoreContainer implements ContainerInterface
{
    #[\Override]
    final public function 
get(string $id): mixed
    
{
        return 
ContainerFactory::getSingleton($id) ?? match ($id) {

            
// ... //

            
default => parent::get($id),
        };
    }
}

Добавление сервиса аналогично его добавлению в классе ContainerFactory.


#Класс ContainerFactory

Фабрика для создания сервисов в качестве singletons, имеет возможность переопределить стандартные сервисы самого фреймворка. Используется для добавления собственных сервисов, которые инициализируются один раз по запросу.

Например, нужно добавить сервис RequestIdService, который возвращает уникальный ID текущего запроса. Это демонстрационный пример сервиса, в основном сервисы представляют более сложные структуры. Добавим его создание в класс ContainerFactory:

<?php
// File /app/Bootstrap/ContainerFactory.php

namespace App\Bootstrap;

use 
App\Bootstrap\Services\RequestIdService;
use 
App\Bootstrap\Services\RequestIdInterface;
use 
Hleb\Constructor\Containers\BaseContainerFactory;

final class 
ContainerFactory extends BaseContainerFactory
{
    public static function 
getSingleton(string $id): mixed
    
{
        
self::has($id) or self::$singletons[$id] = match ($id) {

            
// New service controller.
            
RequestIdInterface::class => new RequestIdService(),

            default => 
null
        
};
        return 
self::$singletons[$id];
    }

    #[\Override]
    public static function 
rollback(): void
    
{
        
self::$singletons = [];
    }
}

Теперь при запросе из контейнера интерфейса RequestIdInterface будет получен экземпляр RequestIdService, который хранится в контейнере как singleton.
Ключом для получения может быть задан не только интерфейс, но и исходный класс RequestIdService, в дальнейшем он именно так будет использован DI (внедрением зависимостей).

Несмотря на то, что выражение match() может содержать несколько ключей к значению, во избежание дублирования сервисов (и как следствие нарушения принципа singleton) назначен должен быть только один.


#Создание метода в контейнере

Чтобы упростить работу с новым сервисом по ключу RequestIdInterface добавим новый метод в контейнер, так будет его проще найти в контейнере через IDE.
Новый метод requestId добавляется в класс контейнера (BaseContainer), теперь класс выглядит вот так:

<?php
// File /app/Bootstrap/BaseContainer.php

namespace App\Bootstrap;

use 
App\Bootstrap\Services\RequestIdInterface;
use 
Hleb\Constructor\Containers\CoreContainer;

final class 
BaseContainer extends CoreContainer implements ContainerInterface
{
    #[\Override]
    final public function 
get(string $id): mixed
    
{
        return 
ContainerFactory::getSingleton($id) ?? match ($id) {

            
// ... //

            
default => parent::get($id),
        };
    }

    
// New method.
    
#[\Override]
    final public function 
requestId(): RequestIdInterface
    
{
        return 
$this->get(RequestIdInterface::class);
    }
}

Важно! Чтобы заработало, метод requestId также должен быть добавлен в интерфейс App\Bootstrap\ContainerInterface.

В примере использовано назначение по интерфейсу сервиса, это позволяет изменить класс сервиса в контейнере, оставив привязку к интерфейсу. Для собственных внутренних классов приложения здесь также можно обойтись без интерфейса, указав соответствие по классу.

Для стандартных сервисов фреймворка все эти действия уже сделаны, их можно получить через соответствующий метод контроллера.

Более подробно создание нового сервиса рассматривается на примере добавления реальной библиотеки.

Создание взаимозависимых сервисов описано в разделе нестандартное использование контейнера.


#Функция rollback() контейнера

Наверняка вы заметили функцию rollback() в классе ContainerFactory. Она необходима для сброса состояния сервисов при асинхронном использовании фреймворка, например, вместе с RoadRunner.

Происходит это так:
Фреймворк при завершении асинхронного запроса сбрасывает состояние у стандартных сервисов.
Потом вызывает данную функцию rollback(), чтобы выполнился указанный в ней код для сброса состояния добавленных вручную сервисов.

Таким образом, если фреймворк используется в асинхронном режиме, то сброс состояния сервиса (как и любого другого модуля) можно инициализировать здесь.

Консольные команды Использование контейнера

Страница создана: @fomiash
К началу страницы