Пакет · di

Справочник API

Полный справочник по публичной поверхности Winter DI. Это авторитетный источник сигнатур и значений по умолчанию; страницы руководств и глубокого погружения ссылаются сюда.

Container

final class Container implements Psr\Container\ContainerInterface. Реестр и резолвер. Он саморегистрируется, поэтому Container и ContainerInterface можно внедрять как зависимости.

Инициализация

php
Container::init(): static
Container::getInstance(): static
Container::isInitialized(): bool
Метод Описание
init() Создаёт новый контейнер и сохраняет его как процессный синглтон. Вызывается один раз при bootstrap. Возвращает экземпляр для fluent-цепочки.
getInstance() Возвращает инициализированный контейнер. Бросает ContainerException, если init() не вызывался.
isInitialized() Существует ли уже контейнер. Для кода, работающего и с контейнером, и без него — библиотеки, доступной как из поднятого приложения, так и из голого скрипта. Спросить дешевле, чем ловить исключение getInstance(): отсутствие контейнера там — законное состояние, а не ошибка.

PSR-11

php
get(string $id): mixed
has(string $id): bool
Метод Описание
get($id) Псевдоним для make($id). Бросает NotFoundException, если у $id нет привязки и это не инстанцируемый класс.
has($id) true, если у $id есть привязка, разрешённое / set()-значение, или совпадает с существующим именем класса (class_exists).

Регистрация

php
bind(string $abstract, string|callable $concrete): static
singleton(string $abstract, string|callable|null $concrete = null): static
transient(string $abstract, string|callable|null $concrete = null): static
request(string $abstract, string|callable|null $concrete = null): static
contextual(string $abstract, callable $factory): static
set(string $id, mixed $value): static
Метод Scope Описание
bind() transient Сопоставляет $abstract конкретному классу или фабрике-замыканию. Новый экземпляр при каждом разрешении.
singleton() singleton Один общий экземпляр на процесс. $concrete по умолчанию $abstract (самопривязка).
transient() transient Как bind(), но допускает самопривязку, когда $concrete равен null.
request() request Один экземпляр на HTTP-запрос / корутину. $concrete по умолчанию $abstract.
contextual() Фабрика, знающая потребителя: fn(Container $c, ?string $consumer). Наложение на время внедрения; результат никогда не кэшируется. См. makeContextual().
set() Сохраняет скаляр или заранее построенный экземпляр под именованным ключом. Извлекается через make($id) или #[Inject('id')].

Фабрика-замыкание, переданная в bind() / singleton() / transient() / request(), получает контейнер: fn(Container $c) => new Service($c->make(Dep::class)).

Ручная регистрация переопределяет атрибуты

Вызов bind() / singleton() / transient() / request() всегда имеет приоритет над scope-атрибутом класса. Повторная регистрация того же абстракта заменяет предыдущую привязку и сбрасывает всё, что оставил прежний scope — закэшированный экземпляр и его учёт для flushRequestScope(), — поэтому переопределение действует и для класса, который уже разрешался.

Разрешение

php
make(string $abstract, array $overrides = []): mixed
call(callable|array $callable, array $overrides = []): mixed
makeContextual(string $abstract, ?string $consumer): mixed

make()

Разрешает абстракт — класс, интерфейс или именованное значение.

Параметр Тип По умолчанию Описание
$abstract string Имя класса/интерфейса или ключ именованного значения.
$overrides array [] Переопределения параметров по имени. Обходит автовайринг для этих параметров.

Порядок разрешения: (1) уже разрешённый синглтон / set()-значение, (2) кэш request-scope в контексте корутины, (3) проверка цикла по стеку текущей единицы работы, (4) ожидание синглтона, который строит другая корутина, (5) ручная привязка, (6) автовайринг по имени класса. Передача $overrides всегда строит свежий экземпляр и никогда не кэшируется. Бросает NotFoundException, если не разрешить, ContainerException при циклической зависимости — с полной цепочкой вида [A] → [B] → [A]. Шаги 3 и 4 разобраны в Конкурентном разрешении.

call()

Вызывает метод или замыкание с параметрами, разрешёнными из контейнера.

php
$c->call([UserController::class, 'index']);   // разрешить класс, затем вызвать
$c->call([$controller, 'store']);              // существующий экземпляр
$c->call(fn(UserService $s) => $s->all());     // замыкание
$c->call([ImportJob::class, 'run'], ['chunkSize' => 100]);  // с overrides
Параметр Тип По умолчанию Описание
$callable callable|array [class-string, method], [object, method] или любой callable/замыкание.
$overrides array [] Переопределения параметров по имени.

makeContextual()

php
makeContextual(string $abstract, ?string $consumer): mixed

Точка входа резолвера для контекстного внедрения. Если для $abstract зарегистрирована фабрика contextual(), она вызывается с $consumer (результат не кэшируется); иначе делегирует make($abstract). Прикладной код обычно использует make() — это существует для механизма внедрения. Контекстное наложение применяется только при внедрении (конструктор / метод / свойство); прямой make()/get() использует обычную привязку.

inject()

php
inject(object $instance): object

Заполняет свойства #[Autowired] / #[Inject] у объекта, который вы построили сами, и возвращает его же. Это вторая половина make(), вынесенная отдельно: make() и строит, и внедряет — что правильно, когда объект принадлежит контейнеру, и неправильно, когда идентичностью объекта должен управлять вызывающий.

php
public static function instance(?string $alias = null): static
{
  $repository = new static();                     // идентичность остаётся за вызывающим
  Container::getInstance()->inject($repository);  // зависимости всё равно приезжают

  return $alias === null ? $repository : $repository->as($alias);
}

Классический случай — хэндл репозитория с алиасом запроса: алиас живёт в состоянии объекта, поэтому два алиаса одной таблицы требуют двух разных объектов, а разрешение их через make() на общей привязке схлопнуло бы оба в один и потеряло бы алиас. Экземпляр никогда не подменяется, состояние конструктора не трогается, повторный вызов безвреден.

flushRequestScope()

php
flushRequestScope(): void

Завершает текущий request-scope: следующее разрешение #[Request]-привязки построит свежий экземпляр. Синглтоны и transient не затрагиваются — это конец scope’а, а не сброс контейнера.

По HTTP scope заканчивается сам: запрос — это корутина, её контекст умирает вместе с ней. Больше ни у чего такой границы нет. Тело долгоживущего воркера целиком выполняется в одной корутине, поэтому request-scoped бин, разрешённый в цикле, живёт весь прогон и передаёт каждой задаче состояние предыдущей:

php
while ($this->isRunning()) {
  $job = $queue->pop();

  $container->flushRequestScope();             // ← здесь начинается новая единица работы
  $ctx = $container->make(JobContext::class);  // свежий, каждый раз
  // ... работа ...
}

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

Провайдеры

php
register(string $providerClass): static

Инстанцирует $providerClass, проверяет, что он наследует ServiceProvider, и сразу выполняет его register(). Бросает ContainerException, если класс не наследует ServiceProvider.

Scanner

Проходит по дереву проекта один раз и передаёт каждый найденный класс всем зарегистрированным коллекторам.

php
Scanner::run(string $rootDir, ?string $cache = null): static
  ->collect(CollectorInterface $collector): static
  ->exclude(array $dirs): static
  ->execute(): void
Метод Описание
run($rootDir, $cache) Создаёт сканер для $rootDir. Когда $cache — путь: попадание в кэш загружает список FQCN и пропускает обход ФС; промах обходит и записывает список. null (по умолчанию) всегда обходит, никогда не кэширует.
collect($collector) Регистрирует коллектор, получающий каждый найденный класс. Вызываются в порядке регистрации.
exclude($dirs) Добавляет абсолютные пути каталогов для исключения. vendor/ исключается всегда.
execute() Запускает сканирование. Абстрактные классы, интерфейсы и трейты пропускаются до запуска коллекторов.

Файл кэша — обычный PHP-файл, возвращающий string[] FQCN. Удалите его, чтобы принудительно пересканировать. См. Сканирование и автообнаружение.

DICollector

final readonly class DICollector implements CollectorInterface. Встроенный коллектор, регистрирующий классы со scope-атрибутом.

php
new DICollector(Container $container)
collect(string $class, ReflectionClass $ref): void
Атрибут класса Регистрация
#[Singleton] $container->singleton($class)
#[Request] $container->request($class)
#[Transient] $container->transient($class)

Классы без scope-атрибута игнорируются (остаются автовайримыми как transient).

CollectorInterface

Реализуйте, чтобы подключить свою логику к проходу Scanner.

php
interface CollectorInterface
{
  // @param class-string $class  FQCN
  // @param ReflectionClass $ref  экземпляр рефлексии
  public function collect(string $class, ReflectionClass $ref): void;
}

collect() вызывается один раз на инстанцируемый класс, в плотном цикле — держите его лёгким.

ServiceProvider

Абстрактная база для группировки привязок.

php
abstract class ServiceProvider
{
  abstract public function register(Container $c): void;
}

Регистрируется через $container->register(MyProvider::class). См. Сервис-провайдеры.

ProxyInterface

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

php
interface ProxyInterface
{
  /** @return class-string  класс, за который выступает этот proxy */
  public static function proxyTarget(): string;
}

Прокси — это сгенерированный класс, наследующий оригинал и добавляющий поведение вокруг его методов: выполнить в фоне, обернуть в транзакцию, закэшировать результат. Именно так устроен #[Async] в ядре Winter. Проблема в том, что после подмены $instance::class возвращает UserService__Async, а не UserService, — и резолвер, который по этому имени решает, для кого строится зависимость, начинает ошибаться: фабрика contextual() назвала бы канал логгера сгенерированным классом.

Реализация интерфейса возвращает идентичность на место: резолвер планирует внедрение от целевого класса и его же передаёт фабрикам как потребителя.

php
final class UserServiceProxy extends UserService implements ProxyInterface
{
  public static function proxyTarget(): string
  {
      return UserService::class;
  }
}

$container->singleton(UserService::class, UserServiceProxy::class);

Метод статический намеренно: идентичность нужна резолверу до того, как экземпляр существует, — при сборке аргументов конструктора. $instance::class продолжает возвращать сгенерированное имя: интерфейс меняет то, как о нём рассуждает контейнер, а не то, что сообщает PHP. Внедрение в приватные свойства цели при этом работает — резолвер обходит иерархию классов, а не полагается на ReflectionClass::getProperties().

ReflectionCache

Кэш объектов рефлексии на процесс — строится один раз, переиспользуется всё время жизни процесса.

php
ReflectionCache::classOf(string $class): ReflectionClass
ReflectionCache::enumOf(string $enum): ReflectionEnum
ReflectionCache::method(string $class, string $method): ReflectionMethod
ReflectionCache::parameters(string $class, string $method): ReflectionParameter[]
Метод Возвращает Примечания
classOf($class) ReflectionClass Кэшируется по классу.
enumOf($enum) ReflectionEnum Кэшируется по enum.
method($class, $method) ReflectionMethod Кэшируется по class::method.
parameters($class, $method) ReflectionParameter[] Делегирует method(); разделяет его запись кэша.

Публичная утилита — см. Кэш рефлексии.

Исключения

Оба реализуют интерфейсы исключений PSR-11 и наследуют \RuntimeException.

Исключение Реализует Бросается когда
NotFoundException Psr\Container\NotFoundExceptionInterface Нет привязки для id, и это не инстанцируемый класс.
ContainerException Psr\Container\ContainerExceptionInterface Циклическая зависимость, неразрешимый параметр, неинициализированный контейнер, неверный провайдер, неверная цель #[Lazy].

Атрибуты

Справочные таблицы по #[Singleton], #[Request], #[Transient], #[Autowired], #[Inject] и #[Lazy] находятся на странице Атрибуты.