Пакет · di

Внедрение в свойства и методы

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

Внедрение по типу через #[Autowired]

Поставьте #[Autowired] на свойство, и контейнер разрешит его по объявленному типу свойства после конструирования объекта. Свойство не обязано быть публичным.

src/SyncCommand.php
<?php

use Flytachi\Winter\DI\Attribute\Autowired;

class SyncCommand extends BaseCommand
{
  #[Autowired]
  private UserService $userService;

  #[Autowired]
  private CacheInterface $cache;

  public function handle(): void
  {
      $this->userService->sync();
  }
}

Внедрение в свойства выполняется автоматически во время make(), сразу после конструктора. Прибегайте к нему, когда конструктор не ваш, чтобы его менять — иначе предпочитайте параметры конструктора.

Интерфейсам всё ещё нужна привязка

#[Autowired] private CacheInterface $cache разрешится, только если у CacheInterface есть привязка — автовайринг не выберет реализацию сам. Привяжите его в провайдере или зарегистрируйте фабрику contextual() для пер-потребительских экземпляров.

Внедрение конкретного класса или именованного значения через #[Inject]

#[Autowired] всегда разрешает по объявленному типу. #[Inject] это переопределяет — внедрить конкретную реализацию или именованное значение, у которого нет осмысленного типа.

php
use Flytachi\Winter\DI\Attribute\Inject;

class ReportService
{
  // конкретная реализация, игнорируя глобальную привязку CacheInterface
  #[Inject(FileCache::class)]
  private CacheInterface $cache;

  // именованный скаляр, зарегистрированный через $container->set('config.timeout', 30)
  #[Inject('config.timeout')]
  private int $timeout;
}

То же работает на параметрах конструктора — переопределите проводку для одного аргумента, не трогая глобальные привязки:

php
class UserService
{
  public function __construct(
      private CacheInterface $primary,                 // глобальная привязка → напр. RedisCache
      #[Inject(FileCache::class)] private CacheInterface $fallback,  // локальное переопределение
      #[Inject('config.timeout')] private int $timeout,             // именованное значение
  ) {}
}

Autowired против Inject

#[Autowired] = разрешить по объявленному типу (явная метка внедрения по типу). #[Inject(X::class)] = внедрить конкретный класс. #[Inject('key')] = внедрить именованное значение, заданное через set(). Пустой #[Inject] без аргумента ведёт себя ровно как #[Autowired].

Вызов метода через call()

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

php
// [class-string, method] — сначала разрешается класс, затем вызывается метод
$container->call([UserController::class, 'index']);

// [object, method] — использовать существующий экземпляр
$container->call([$controller, 'store']);

// замыкание — разрешается каждый типизированный параметр
$container->call(fn(UserService $s, AuthContext $a) => $s->current($a->user()));

Передавайте runtime-значения, которые контейнер не может автовайрить (id, размеры, данные запроса), через overrides, по имени параметра:

php
class ImportJob
{
  public function run(UserService $service, int $chunkSize): void { /* ... */ }
}

// $service автовайрится; $chunkSize приходит из override
$container->call([ImportJob::class, 'run'], ['chunkSize' => 500]);

Тот же массив overrides работает у make() для параметров конструктора:

php
$job = $container->make(ImportJob::class, ['chunkSize' => 500]);

Overrides обходят кэш синглтона

Передача массива overrides всегда строит свежий экземпляр — результат не читается из кэша singleton/request и не пишется в него, поскольку переопределённый объект не «канонический». Вызовите make() без overrides, чтобы получить общий экземпляр.

Связанное