Обидва кажуть контейнеру, як створювати сервіс. Різниця - у тому, скільки разів це станеться.
bind() створює новий екземпляр щоразу:
$this->app->bind(ReportBuilder::class, function ($app) {
return new ReportBuilder($app->make(Connection::class));
});
app(ReportBuilder::class) === app(ReportBuilder::class); // false
singleton() створює один раз і повертає той самий обʼєкт до кінця запиту:
$this->app->singleton(WeatherClient::class, function ($app) {
return new WeatherClient(config('services.weather.key'));
});
app(WeatherClient::class) === app(WeatherClient::class); // true
Є ще scoped() - як singleton, але скидається на кожному запиті. Це важливо для Octane, де застосунок живе між запитами й звичайний singleton зберігав би стан довше, ніж треба.
Що обирати. Singleton доречний, коли створення дороге (клієнт із конфігурацією, зʼєднання) або коли стан має бути спільним. bind() - коли обʼєкт накопичує стан і два різних місця не повинні його ділити.
Головна пастка singleton - утримання стану. Сервіс, що накопичує щось усередині, віддасть ці дані наступному, хто його запросить:
class Basket
{
private array $items = [];
public function add(Item $item): void
{
$this->items[] = $item; // у singleton лишиться на весь запит
}
}
Під Octane це вже не «на запит», а між запитами - і дані одного користувача протікають до іншого. Тому в singleton кладуть сервіси без стану, а стан тримають явно.