Dependency Injection (DI) to fundament pracy z frameworkiem Symfony. Właściwe zrozumienie tego mechanizmu jest kluczowe, zwłaszcza gdy projekt staje się coraz większy i pojawiają się dziesiątki (a nawet setki) serwisów. W tym wpisie pokażę, jak działa Service Container w Symfony oraz jakie są różnice między autowiringiem, aliasami i ręcznym definiowaniem serwisów w services.yaml.
Czym jest Dependency Injection w Symfony?
Kontener usług w Symfony to centralne miejsce odpowiedzialne za tworzenie i dostarczanie obiektów w aplikacji. Dzięki niemu klasy nie tworzą zależności samodzielnie (new Logger()), tylko otrzymują je z zewnątrz – np. poprzez konstruktor.
To podejście ma trzy ogromne zalety:
- zwiększa testowalność kodu (łatwiej wstrzykiwać mocki),
- poprawia czytelność i modularność,
- ułatwia zarządzanie cyklem życia obiektów (np. lazy loading).
Autowiring – szybkie i wygodne
Autowiring pozwala Symfony automatycznie dopasować zależności do parametrów konstruktora na podstawie type-hintów.
Przykład:
use Psr\Log\LoggerInterface;
class MailService {
public function __construct(private LoggerInterface $logger) {}
}
Symfony samo wstrzyknie odpowiedni serwis (monolog.logger), bez dodatkowej konfiguracji.
Zalety:
- minimalna konfiguracja,
- szybkie prototypowanie,
- czystszy kod.
Wady:
- problematyczne, gdy istnieje więcej niż jedna implementacja tego samego interfejsu.
- czasem wymaga aliasów lub jawnej konfiguracji.
Alias to mapowanie jednego serwisu na inny. Przydaje się szczególnie wtedy, gdy mamy interfejs i kilka implementacji, a chcemy wskazać, która powinna być używana domyślnie.
Przykład (services.yaml):
App\Service\MailerInterface: '@App\Service\SmtpMailer'
Od teraz, każda klasa wymagająca MailerInterface dostanie SmtpMailer.
Zastosowania aliasów:
- wybór domyślnej implementacji interfejsu,
- możliwość łatwego podmienia serwisu w środowisku
dev/test/prod, - nadawanie krótszych nazw serwisom.
Ręczne definiowanie serwisów w services.yaml
Czasami autowiring to za mało. Jeśli serwis wymaga parametrów konfiguracyjnych albo musi być zbudowany w niestandardowy sposób, stosuje się ręczne definicje.
Przykład:
App\Service\ApiClient:
arguments:
$apiKey: '%env(API_KEY)%'
$timeout: 30
Tutaj Symfony wstrzyknie do konstruktora serwisu wartości zdefiniowane w konfiguracji.
Zastosowania ręcznej konfiguracji:
- gdy serwis potrzebuje parametrów konfiguracyjnych,
- gdy korzysta z fabryki lub statycznej metody tworzącej,
- gdy autowiring nie jest możliwy (np. brak jednoznacznych type-hintów).
Jak to łączyć w praktyce?
W realnych projektach najczęściej stosuje się hybrydowe podejście:
- autowiring dla większości serwisów,
- aliasy do wskazania właściwej implementacji interfejsów,
- ręczne definicje dla bardziej złożonych przypadków (API, fabryki, parametry).
To pozwala utrzymać czytelność, elastyczność i kontrolę nad projektem, jednocześnie nie obciążając kodu nadmiarem konfiguracji.
Podsumowanie
- Autowiring – szybki start i minimalna konfiguracja.
- Alias – wybór implementacji interfejsu, łatwe nadpisywanie serwisów.
- Ręczne definiowanie – kontrola i elastyczność w złożonych scenariuszach.
Jako senior developer powinieneś nie tylko znać różnice, ale też świadomie wybierać podejście zależnie od kontekstu projektu. Symfony daje elastyczność – sztuką jest użyć jej mądrze.
