Skip to content
github-actions[bot] edited this page Jul 8, 2026 · 1 revision

🔒 Scoped-сервисы

Статус: v1.19.0 (#33) · аддитивно, PHP 8.1+, breaking нет.

Scoped-сервис — это третий режим времени жизни между singleton (get() — один на контейнер) и prototype (make() — новый на каждый вызов): один экземпляр в пределах активного scope, новый — в каждом новом scope.

Типичный сценарий — жизнь запроса: RequestContext, UnitOfWork, соединение с БД на HTTP-запрос или задачу очереди. Внутри одного запроса — один экземпляр; следующий запрос получает свежий.


Быстрый старт

use CloudCastle\DI\Container;

$container = new Container();
$container->scoped('request.context', static fn (): RequestContext => new RequestContext());

$container->beginScope();               // начало запроса
$a = $container->get('request.context');
$b = $container->get('request.context');
$a === $b;                              // true — один экземпляр в scope
$container->endScope();                 // конец запроса — экземпляр уничтожается

$container->beginScope();               // следующий запрос
$c = $container->get('request.context');
$c === $a;                              // false — новый экземпляр
$container->endScope();

API

Метод Назначение
scoped(string $id, callable|string $concrete) Регистрирует scoped-сервис. $concrete — фабрика callable(self): mixed или FQCN класса для autowiring
beginScope() Открывает новый scope (вложенные — через стек LIFO)
endScope() Закрывает текущий scope и уничтожает его scoped-экземпляры
inScope(): bool Активен ли хотя бы один scope
isScoped(string $id): bool Зарегистрирован ли сервис как scoped
scopeDepth(): int Глубина вложенности активных scope-ов (0 — вне scope)

Регистрация

// Через фабрику (получает контейнер):
$container->scoped('uow', static fn (Container $c): UnitOfWork => new UnitOfWork($c->get('db')));

// Через FQCN (autowiring в каждом scope):
$container->scoped(UnitOfWork::class, UnitOfWork::class);

Поведение вне scope

Вне активного scope scoped-сервис ведёт себя как transient — фабрика вызывается на каждый get(), экземпляр не кэшируется:

$container->scoped('svc', static fn () => new Service());

$x = $container->get('svc');
$y = $container->get('svc');
$x === $y;              // false — вне scope каждый get() создаёт новый
$container->inScope();  // false

Это безопасный дефолт: код, использующий scoped-сервис без открытого scope, продолжает работать (как make()), а не падает.


Вложенные scope-ы

beginScope() можно вкладывать — каждый уровень имеет свой кэш (стек LIFO). Вложенный scope изолирован от внешнего; после endScope() возобновляется экземпляр внешнего:

$container->beginScope();
$outer = $container->get('svc');

$container->beginScope();
$inner = $container->get('svc');
$inner === $outer;               // false — вложенный scope изолирован
$container->scopeDepth();        // 2
$container->endScope();

$container->get('svc') === $outer; // true — вернулись во внешний scope
$container->endScope();

endScope() без активного scope бросает ContainerException.


Сравнение режимов

Режим Метод Время жизни
Singleton set() + get() Один на контейнер
Prototype make() Новый на каждый вызов
Scoped scoped() + get() Один на активный scope, новый — в каждом scope

Compiled container

На runtime Container доступен полный scoped-API. В compiled-контейнере работает жизненный цикл scope (beginScope/endScope/inScope/scopeDepth), но регистрация scoped-сервисов недоступна — compiled-контейнер immutable, определения зафиксированы на этапе компиляции. Регистрируйте scoped-сервисы на runtime-контейнере до freeze()/компиляции.


Типичный сценарий: HTTP-запрос

$container->scoped('request', static fn () => new RequestContext());

foreach ($requests as $httpRequest) {
    $container->beginScope();
    try {
        $handler = $container->get(RequestHandler::class);
        $handler->handle($httpRequest);   // все get('request') внутри — один контекст
    } finally {
        $container->endScope();           // гарантированная очистка scope
    }
}

Оборачивайте endScope() в finally, чтобы scope закрывался даже при исключении.


См. также: API-reference · Фабрики и singleton · Прототипы, alias, lazy

Clone this wiki locally