Сервіс-контейнер - механізм, що створює об'єкти й автоматично передає їм залежності. Клас оголошує, що йому потрібно, у конструкторі - контейнер підставляє.
final class PlaceOrder
{
public function __construct(
private PaymentGateway $payments,
private InventoryService $inventory,
private Dispatcher $events,
) {}
}
// контролер: контейнер створить PlaceOrder і всі його залежності
public function store(StoreOrderRequest $request, PlaceOrder $placeOrder) { /* ... */ }
Розв'язання без конфігурації: для конкретних класів контейнер сам читає конструктор через рефлексію й рекурсивно створює залежності. Реєструвати нічого не треба.
Коли потрібна реєстрація (у AppServiceProvider::register()):
- інтерфейс → реалізація:
$this->app->bind(PaymentGateway::class, StripeGateway::class);
- один екземпляр на застосунок (з'єднання, клієнти API):
singleton(); на запит чи задачу черги -scoped(); - параметри конструктора, яких контейнер не знає (ключі, URL):
$this->app->singleton(StripeGateway::class, fn () => new StripeGateway(config('services.stripe.secret')));
- різні реалізації для різних споживачів - контекстна прив'язка (
when(...)->needs(...)->give(...)) чи атрибути (#[Config('...')],#[Storage('s3')]).
Де контейнер впроваджує залежності автоматично: контролери (конструктор і методи), джоби (handle), слухачі, команди Artisan, middleware, Form Request, політики, Livewire-компоненти.
Навіщо це все:
- тестування: підміна залежності фейком -
$this->app->instance(PaymentGateway::class, new FakeGateway)чи$this->mock(...); - слабка зв'язність: клас залежить від інтерфейсу, а конкретну реалізацію обирає конфігурація;
- явні залежності: з конструктора видно, з чим працює клас.
Чого уникати:
app()/resolve()всередині методів (service locator) - залежність прихована, як і в Singleton;- логіки в конструкторах (запити до бази, HTTP) - конструктор має лише зберегти залежності;
- реєстрації всього підряд - для конкретних класів без параметрів вона не потрібна.
Докладніше в документації: Laravel: розв'язання без конфігурації