Що таке service container і як він знаходить залежності?
Контейнер відповідає за створення обʼєктів і підстановку залежностей: читає типи параметрів конструктора через рефлексію і рекурсивно створює їх.
Як питають
Як Laravel розуміє, що передати в конструктор контролера?
Чим bind відрізняється від singleton?
Навіщо потрібні service providers?
service containerDIпровайдери
Пояснення
Service container — це реєстр того, як створювати обʼєкти. Коли Laravel потрібен контролер, listener, job чи будь-який клас, він просить контейнер, а той через рефлексію читає типи параметрів конструктора й рекурсивно створює кожну залежність. Для конкретних класів з типізованими конструкторами реєстрація не потрібна.
Привʼязки у service provider потрібні там, де рефлексії недостатньо: інтерфейс, який треба звʼязати з реалізацією, скалярний параметр із конфігурації, обʼєкт із дорогою ініціалізацією. Три основні методи відрізняються життєвим циклом: bind дає новий екземпляр щоразу, singleton один на весь час життя контейнера, scoped один на запит. Контекстна привʼязка через when()->needs()->give() дозволяє дати різним споживачам різні реалізації того самого інтерфейсу.
Головна практична користь контейнера в тестах. Клас, який оголошує залежності в конструкторі, можна зібрати з фейковим репозиторієм одним викликом instance(), не чіпаючи код. Фасад чи app() всередині методу ховають залежність, і підмінити її складніше. Довгий список параметрів у конструкторі теж корисний сигнал: клас робить забагато.
КодPHP
// 1. Конкретний клас: реєстрація не потрібна, контейнер читає типи конструктора
final class InvoiceService
{
public function __construct(
private readonly InvoiceRepository $invoices, // інтерфейс: потрібна привʼязка
private readonly PdfRenderer $pdf, // клас: створиться сам
) {}
}
// 2. Привʼязки у AppServiceProvider::register()
$this->app->bind(InvoiceRepository::class, EloquentInvoiceRepository::class);
$this->app->singleton(PdfRenderer::class, fn () => new PdfRenderer(config('pdf.binary')));
$this->app->scoped(RequestContext::class); // один на запит, безпечно під Octane
// 3. Контекстна привʼязка: різні реалізації для різних споживачів
$this->app->when(ReportExporter::class)
->needs(Filesystem::class)
->give(fn () => Storage::disk('reports'));
// 4. У тесті інтерфейс підміняється без правки коду
$this->app->instance(InvoiceRepository::class, new InMemoryInvoiceRepository);
Що хоче почути інтервʼюер
Що autowiring базується на type-hint: контейнер дивиться на клас параметра конструктора, а не на імʼя змінної.
Що інтерфейс сам себе створити не може, тому для нього потрібна привʼязка у service provider: bind або singleton.
Різницю bind, singleton і scoped: новий обʼєкт щоразу, один на весь процес, один на запит.
Що контекстна привʼязка (when-needs-give) дозволяє дати різні реалізації різним споживачам.
Чому залежність у конструкторі краща за фасад або app() всередині методу: явні залежності, простіші тести, видно, що клас робить забагато.
Типові помилки
Казати, що контейнер створює лише зареєстровані класи: конкретні класи з типізованими конструкторами створюються без жодної реєстрації.
Плутати singleton контейнера з патерном Singleton у класі: перший керується ззовні і легко підміняється в тестах.
Використовувати singleton для обʼєкта, який зберігає дані запиту: під Octane такий стан протече між користувачами.
Не розуміти, що скалярний параметр без значення за замовчуванням контейнер сам не розвʼяже й кине BindingResolutionException.
ПОРАДА
Покажіть, що розумієте різницю між bind і singleton і чому інтерфейс у конструкторі кращий за фасад у тестах. Згадайте scoped як відповідь на Octane.
Додаткові питанняЗ ВІДПОВІДЯМИ
bind створює новий екземпляр при кожному make або інʼєкції. singleton створює обʼєкт один раз і повертає той самий до кінця життя контейнера, що у звичайному PHP-FPM означає до кінця запиту, а під Octane до перезапуску воркера. scoped це singleton, який скидається після кожного запиту.
Контейнер не знає, що підставити. Якщо у параметра є значення за замовчуванням, візьме його; якщо ні, кине BindingResolutionException з текстом Unresolvable dependency. Скалярні залежності передають через контекстну привʼязку або через замикання у bind, яке читає config.
Інтерфейс не можна інстанціювати, тому контейнер кине BindingResolutionException: Target [App\Contracts\Foo] is not instantiable. Виправлення: $this->app->bind(Foo::class, EloquentFoo::class) у провайдері або атрибут #[Bind] на інтерфейсі у Laravel 12+.
У точках входу, де конструктор недоступний: старі хелпери, фабрики, код поза контейнером. У сервісах і контролерах це антипатерн, бо ховає залежності від читача й тестів. Виняток: лінива інʼєкція через замикання, коли обʼєкт дорогий і потрібен не завжди.