<? phpukraine СПІВБЕСІДИ
Пошук по платформі
LARAVEL · JUNIOR ЧАСТО ПИТАЮТЬ

Що таке service container і як він знаходить залежності?

Контейнер відповідає за створення обʼєктів і підстановку залежностей: читає типи параметрів конструктора через рефлексію і рекурсивно створює їх.

Як Laravel розуміє, що передати в конструктор контролера?
Чим bind відрізняється від singleton?
Навіщо потрібні service providers?
service container DI провайдери

Service container — це реєстр того, як створювати обʼєкти. Коли Laravel потрібен контролер, listener, job чи будь-який клас, він просить контейнер, а той через рефлексію читає типи параметрів конструктора й рекурсивно створює кожну залежність. Для конкретних класів з типізованими конструкторами реєстрація не потрібна.

Привʼязки у service provider потрібні там, де рефлексії недостатньо: інтерфейс, який треба звʼязати з реалізацією, скалярний параметр із конфігурації, обʼєкт із дорогою ініціалізацією. Три основні методи відрізняються життєвим циклом: bind дає новий екземпляр щоразу, singleton один на весь час життя контейнера, scoped один на запит. Контекстна привʼязка через when()->needs()->give() дозволяє дати різним споживачам різні реалізації того самого інтерфейсу.

Головна практична користь контейнера в тестах. Клас, який оголошує залежності в конструкторі, можна зібрати з фейковим репозиторієм одним викликом instance(), не чіпаючи код. Фасад чи app() всередині методу ховають залежність, і підмінити її складніше. Довгий список параметрів у конструкторі теж корисний сигнал: клас робить забагато.

// 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.

оновлено 3 вересня 2026 · ліцензія CC-BY-SA-4.0 Знайшли неточність? Напишіть →
ПЕРЕВІРТЕ СЕБЕ

Autowiring базується на type-hint параметрів; провайдери потрібні для інтерфейсів і особливих випадків.