Dependency Injection w Symfony – autowiring, aliasy i ręczne definiowanie serwisów


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.