Методом extend() контейнера - він отримує вже створений сервіс і повертає те, що віддаватиметься всім споживачам.
// AppServiceProvider::register()
$this->app->extend(GeocoderInterface::class, function (GeocoderInterface $geocoder, Application $app) {
return new CachedGeocoder($geocoder, $app->make('cache.store'));
});
final class CachedGeocoder implements GeocoderInterface
{
public function __construct(private GeocoderInterface $inner, private Repository $cache) {}
public function locate(string $address): Coordinates
{
return $this->cache->remember('geo:' . md5($address), 86400, fn () => $this->inner->locate($address));
}
}
Це патерн «декоратор»: той самий інтерфейс, а всередині - оригінал плюс нова поведінка. Код, що просить GeocoderInterface, нічого не помічає.
Чому це краще за альтернативи:
- не треба наслідувати клас пакета й підміняти прив'язку - пакет може змінити конструктор;
- декоратори нашаровуються: кеш поверх журналу поверх повторів, кожен простий і тестується окремо.
Суміжні інструменти:
resolving()- викликати код щоразу, коли сервіс створюється (налаштувати, а не замінити);tag()іtagged()- зібрати всі реалізації інтерфейсу, щоб передати їх масивом.