Scope’ы
Scope определяет, как долго живёт разрешённый экземпляр и является ли он общим. Их три:
singleton, transient и request. Класс выбирает один атрибутом или привязкой; по
умолчанию — transient.
Три scope’а
| Scope | Идентичность | Кэшируется | Задаётся |
|---|---|---|---|
singleton |
один на процесс | да, после первого make() |
#[Singleton] · singleton() |
transient |
новый при каждом разрешении | нет | #[Transient] · bind() · transient() · (по умолчанию) |
request |
один на запрос / корутину | на запрос | #[Request] · request() |
singleton
Создаётся при первом make(), кэшируется в контейнере, возвращается при каждом последующем
вызове.
Для stateless-сервисов — репозиториев, фабрик, пулов соединений, читателей конфигурации. Избегать для классов с пер-запросным состоянием; оно утечёт между запросами под Swoole.
transient
Без кэширования — каждый make() / внедрение возвращает свежий объект. Это вариант по
умолчанию, когда у класса нет scope-атрибута и ручной привязки.
Для объектов с состоянием, которые не должны быть общими — билдеров запросов, form-объектов, DTO, unit of work.
request
Один экземпляр на HTTP-запрос, в зависимости от рантайма:
| Рантайм | Поведение |
|---|---|
| Swoole | Хранится в Coroutine::getContext()['__di'] — полностью изолирован по корутинам. Каждый параллельный запрос получает свой экземпляр, очищается по завершении корутины. |
| FPM | Эквивалентно singleton — один запрос = один процесс, поэтому кэш на уровне процесса корректен. |
| CLI | Эквивалентно singleton — одна команда = один процесс. |
request-scope — единственное место, где важен ext-swoole; см.
Request-scope и Swoole.
Приоритет
Ручная регистрация всегда переопределяет scope-атрибут класса — побеждает последняя привязка.
#[Singleton]
class UserService {}
// Переопределить в transient для конкретного контекста (напр. тестов)
$c->transient(UserService::class);
// → UserService теперь transient, независимо от #[Singleton]Переопределение действует и тогда, когда класс уже разрешался: любая регистрация
(bind() / singleton() / transient() / request()) сбрасывает то, что оставил предыдущий
scope, — и закэшированный экземпляр, и учёт для flushRequestScope(). Поэтому в тесте можно
переключить scope посреди сценария и сразу получить объект по новым правилам, а не старый
синглтон из кэша.
Overrides полностью обходят кэш
make($abstract, $overrides) с непустым массивом $overrides всегда строит свежий экземпляр и
не читает и не пишет кэш singleton/request — независимо от scope. Используйте make() без
overrides, чтобы получить общий экземпляр.
Матрица безопасности Swoole
Никогда не помещайте изменяемые пер-запросные данные в singleton под Swoole — они утекут
между параллельными запросами, разделяющими память воркера.
| Тип класса | Рекомендуемый scope |
|---|---|
| Пул соединений с БД | singleton |
| Репозиторий (stateless) | singleton |
| Читатель конфигурации | singleton |
| Контекст авторизации | request |
| Текущий пользователь | request |
| Unit of work | request |
| Билдер запросов | transient |
| DTO / value object | transient |
| Form-объект | transient |
Под нагрузкой
Когда один воркер Swoole обслуживает много запросов одновременно, scope решает и то, что
происходит при одновременном разрешении одного класса: transient и request строят свои
экземпляры, а singleton строится ровно один раз — остальные корутины дожидаются первого
вместо того, чтобы собирать копию. Подробности — на странице
Конкурентное разрешение.
Связанное
- Атрибуты — справочник по scope-атрибутам
- Request-scope и Swoole — изоляция по корутинам подробно
- Конкурентное разрешение — одновременный
make()одного класса - Ментальная модель — почему по умолчанию
transient