# Wstrzykiwanie zależności w Symfony: kontener usług, autowiring i tagi w 2026 > Dogłębna analiza wstrzykiwania zależności w Symfony: kontener usług, autowiring, atrybut #[Autowire], wiązanie interfejsów, otagowane usługi z #[AutowireIterator], compiler passes i usługi leniwe w Symfony 7.4 i 8.0. - Published: 2026-07-06 - Updated: 2026-07-07 - Author: SharpSkill - Tags: symfony, dependency injection, service container, autowiring, php, di - Reading time: 11 min --- Wstrzykiwanie zależności (dependency injection) to fundament każdej aplikacji Symfony: kontener usług tworzy, konfiguruje i łączy obiekty ze sobą, dzięki czemu klasy deklarują to, czego potrzebują, zamiast samodzielnie budować swoje zależności. W Symfony 7.4 i 8.0 autowiring, atrybuty PHP oraz otagowane usługi ograniczyły jawną konfigurację niemal do zera, a jednocześnie kontener pozostaje w pełni rozwiązany i przewidywalny na etapie kompilacji. Zrozumienie tego mechanizmu rozdziela programistów, którzy walczą z kontenerem, od tych, którzy pozwalają mu wykonać pracę za siebie. > **Czym jest wstrzykiwanie zależności w Symfony?** > > Wstrzykiwanie zależności w Symfony to wzorzec, w którym kontener usług przekazuje zależności obiektu przez jego konstruktor, zamiast pozwalać obiektowi samodzielnie je tworzyć. Autowiring automatycznie dopasowuje każdy argument z podpowiedzią typu (type hint) do odpowiadającej mu usługi, dzięki czemu większość klas nie wymaga żadnej ręcznej konfiguracji. ## Jak kontener usług Symfony rozwiązuje zależności Kontener usług to skompilowana klasa PHP, która wie, jak zbudować każdą usługę w aplikacji. Podczas fazy kompilacji kontenera Symfony skanuje definicje klas, odczytuje sygnatury konstruktorów i tworzy zapisaną w pamięci podręcznej fabrykę. W czasie działania aplikacji żądanie usługi zwraca leniwie budowaną, współdzieloną instancję. Ponieważ łączenie zależności odbywa się na etapie kompilacji, źle skonfigurowana zależność zawodzi podczas budowania pamięci podręcznej, a nie wtedy, gdy żądanie trafia na produkcję. Usługa to po prostu klasa, która wykonuje jakieś zadanie i jest zarządzana przez kontener. Domyślny plik `config/services.yaml` rejestruje wszystko z katalogu `src/` jako usługę z włączonym autowiringiem i autokonfiguracją, dlatego zwykła klasa działa bez żadnej dodatkowej konfiguracji. ```yaml # config/services.yaml services: _defaults: autowire: true # inject arguments by type-hint autoconfigure: true # apply tags based on implemented interfaces App\: resource: '../src/' exclude: - '../src/Entity/' - '../src/Kernel.php' ``` Przy takich ustawieniach domyślnych klasa prosi o współpracowników przez swój konstruktor, a kontener je dostarcza. Żadnej fabryki, żadnego `new`, żadnej ręcznej listy argumentów. ```php // src/Notification/SlackNotifier.php namespace App\Notification; use Psr\Log\LoggerInterface; use Symfony\Contracts\HttpClient\HttpClientInterface; final class SlackNotifier { // The container reads each type-hint and injects a matching service public function __construct( private readonly HttpClientInterface $httpClient, private readonly LoggerInterface $logger, ) {} public function send(string $channel, string $message): void { $this->httpClient->request('POST', 'https://slack.example/api/post', [ 'json' => ['channel' => $channel, 'text' => $message], ]); $this->logger->info('Slack message dispatched', ['channel' => $channel]); } } ``` Kontener tworzy `SlackNotifier` jednorazowo, wstrzykuje współdzielonego klienta HTTP oraz logger i ponownie wykorzystuje tę instancję wszędzie tam, gdzie jest ona żądana. To domyślne współdzielenie sprawia, że kontener jest zarówno szybki, jak i oszczędny pod względem pamięci. Pełny model opisano w oficjalnym [przewodniku po kontenerze usług Symfony](https://symfony.com/doc/current/service_container.html), który opiera się na standardzie [interfejsu kontenera PSR-11](https://www.php-fig.org/psr/psr-11/) wspólnym dla całego ekosystemu PHP. Współdzielenie można wyłączyć, gdy przy każdym żądaniu potrzebna jest świeża instancja, na przykład dla stanowego buildera, który gromadzi dane. Ustawienie `shared: false` w definicji zamienia ją w fabrykę zwracającą nowy obiekt przy każdym wstrzyknięciu. W praktyce zdarza się to rzadko, ponieważ większość usług to bezstanowi współpracownicy, jednak świadomość istnienia tego domyślnego zachowania i sposobu jego nadpisania ma znaczenie, gdy błąd okazuje się wynikać z dwóch konsumentów nieoczekiwanie modyfikujących ten sam obiekt. ## Autowiring w Symfony: podpowiedzi typów i atrybut Autowire Autowiring w Symfony rozwiązuje argumenty konstruktora na podstawie ich deklaracji typu. Gdy argument ma podpowiedź typu w postaci klasy lub interfejsu odpowiadającego dokładnie jednej usłudze, kontener wstrzykuje ją bez żadnej konfiguracji. Autowiring przestaje działać jedynie dla wartości, które nie są usługami: wartości skalarnych, zmiennych środowiskowych i parametrów. Atrybut `#[Autowire]` domyka tę lukę bezpośrednio przy argumencie, utrzymując konfigurację połączeń tuż obok kodu, który z nich korzysta. ```php // src/Service/PaymentGateway.php namespace App\Service; use Psr\Log\LoggerInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; final class PaymentGateway { public function __construct( // Inject a container parameter with the %...% syntax #[Autowire('%kernel.environment%')] private readonly string $environment, // Inject an environment variable, resolved at runtime #[Autowire(env: 'STRIPE_SECRET')] private readonly string $apiKey, // Bind a specific service when the type alone is ambiguous #[Autowire(service: 'monolog.logger.payment')] private readonly LoggerInterface $logger, ) {} } ``` Każdy argument deklaruje własne źródło: parametr, zmienną środowiskową albo nazwaną usługę. Atrybut przyjmuje również argument `expression:` dla wartości wyrażonych w Expression Language, co przydaje się, gdy zależność pochodzi z obliczenia w czasie działania, a nie ze statycznej definicji. Trzymanie tych metadanych przy konstruktorze sprawia, że czytający widzi dokładnie, skąd pochodzi każda wartość, bez otwierania osobnego pliku YAML. Dokumentacja atrybutu znajduje się w [dokumentacji autowiringu Symfony](https://symfony.com/doc/current/service_container/autowiring.html). > **Podpowiadaj typy interfejsów, a nie konkretnych klas** > > Autowiring konkretnej klasy, takiej jak `S3Storage`, wiąże każdego konsumenta z tą implementacją i utrudnia jej podmianę w testach. Zamiast tego należy podpowiadać typ interfejsu i pozwolić, aby `#[AsAlias]` lub `#[Autowire(service: ...)]` decydowało o konkretnej usłudze. Wstrzykiwanie całego kontenera po to, aby ręcznie pobierać z niego usługi, niweczy kontrole na etapie kompilacji i jest właśnie tym antywzorcem, który autowiring ma eliminować. ## Wiązanie interfejsów, gdy istnieje wiele implementacji Autowiring względem interfejsu działa bez zarzutu do momentu, gdy dwie klasy go implementują. Wtedy kontener nie potrafi odgadnąć, którą z nich wstrzyknąć, i zgłasza błąd niejednoznaczności na etapie kompilacji. Rozwiązują to dwa atrybuty: `#[AsAlias]` oznacza jedną implementację jako domyślną dla interfejsu, a `#[Autowire(service: ...)]` przypina konkretną implementację do pojedynczego argumentu. ```php // src/Storage/StorageInterface.php namespace App\Storage; interface StorageInterface { public function put(string $key, string $contents): void; } ``` ```php // src/Storage/S3Storage.php namespace App\Storage; use Symfony\Component\DependencyInjection\Attribute\AsAlias; // Becomes the default service returned for StorageInterface #[AsAlias(StorageInterface::class)] final class S3Storage implements StorageInterface { public function put(string $key, string $contents): void { /* upload to S3 */ } } ``` ```php // src/Service/BackupService.php namespace App\Service; use App\Storage\LocalStorage; use App\Storage\StorageInterface; use Symfony\Component\DependencyInjection\Attribute\Autowire; final class BackupService { public function __construct( // Resolves to the #[AsAlias] default (S3Storage) private readonly StorageInterface $primary, // Pins the concrete implementation explicitly by service id #[Autowire(service: LocalStorage::class)] private readonly StorageInterface $fallback, ) {} } ``` Atrybut `#[Target]` oferuje trzecią opcję: odwołuje się do implementacji przez jej nazwę aliasu autowiringu, oddzielając nazwę zmiennej argumentu od sposobu rozwiązania. Wybór między nimi sprowadza się do intencji. Warto sięgnąć po `#[AsAlias]`, gdy jedna implementacja jest faktycznie domyślna w całym kodzie, oraz po `#[Autowire(service: ...)]` lub `#[Target]`, gdy pojedynczy konsument potrzebuje wariantu innego niż domyślny. Ten wzorzec pojawia się nieustannie w aplikacjach produkcyjnych i jest ulubionym tematem [modułu pytań rekrutacyjnych o wstrzykiwanie zależności w Symfony](/technologies/symfony/interview-questions/dependency-injection). ## Tagi usług Symfony i kolektor AutowireIterator Tagi to sposób, w jaki Symfony grupuje powiązane usługi, aby inna usługa mogła skorzystać z nich wszystkich naraz. Napędza to wzorce strategii, łańcucha odpowiedzialności i wtyczek, które pojawiają się w całym frameworku: rozszerzenia typów formularzy, rozszerzenia Twig oraz subskrybenci zdarzeń są sterowane tagami. Zamiast tagować ręcznie, atrybut `#[AutoconfigureTag]` umieszczony na interfejsie każe kontenerowi automatycznie otagować każdą implementującą go klasę. ```php // src/Export/ExporterInterface.php namespace App\Export; use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; // Every class implementing this interface is tagged automatically #[AutoconfigureTag('app.exporter')] interface ExporterInterface { public function supports(string $format): bool; public function export(array $rows): string; } ``` Poszczególne implementacje kontrolują swoją pozycję w kolekcji za pomocą `#[AsTaggedItem]`, który ustawia priorytet (wyższy uruchamia się jako pierwszy) oraz opcjonalny indeks tekstowy umożliwiający dostęp po kluczu. ```php // src/Export/CsvExporter.php namespace App\Export; use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem; // Higher priority services appear earlier in the injected iterable #[AsTaggedItem(priority: 10)] final class CsvExporter implements ExporterInterface { public function supports(string $format): bool { return $format === 'csv'; } public function export(array $rows): string { return implode("\n", array_map(fn ($r) => implode(',', $r), $rows)); } } ``` Usługa konsumująca zbiera wszystkie otagowane eksportery w uporządkowany obiekt `iterable` przy użyciu `#[AutowireIterator]`. Następnie przechodzi przez kolekcję i deleguje zadanie do pierwszej strategii, która obsługuje żądany format. ```php // src/Export/ExportManager.php namespace App\Export; use Symfony\Component\DependencyInjection\Attribute\AutowireIterator; final class ExportManager { // Collects all services tagged 'app.exporter', ordered by priority public function __construct( #[AutowireIterator('app.exporter')] private readonly iterable $exporters, ) {} public function export(string $format, array $rows): string { foreach ($this->exporters as $exporter) { if ($exporter->supports($format)) { return $exporter->export($rows); } } throw new \InvalidArgumentException("No exporter for format: $format"); } } ``` Gdy kolekcja jest duża lub kosztowna w budowie, `#[AutowireLocator]` stanowi leniwą alternatywę: wstrzykuje kontener zgodny z PSR-11, który tworzy otagowaną usługę dopiero wtedy, gdy zostanie ona faktycznie pobrana po kluczu. To rozróżnienie między zachłanną iteracją a leniwym wyszukiwaniem ma znaczenie dla usług przechowujących połączenia lub obciążony stan. ## Autokonfiguracja, compiler passes i usługi leniwe Autokonfiguracja to mechanizm, który odczytuje atrybuty takie jak `#[AutoconfigureTag]` i `#[AsTaggedItem]` oraz stosuje je podczas kompilacji. To dzięki niej samo zaimplementowanie `EventSubscriberInterface` wystarcza, aby zarejestrować subskrybenta zdarzeń bez żadnej konfiguracji. W przypadku transformacji, których nie da się wyrazić atrybutami, compiler pass wpina się bezpośrednio w proces budowy kontenera i modyfikuje definicje, zanim pamięć podręczna zostanie zapisana. Źródła komponentu `DependencyInjection` na [GitHubie](https://github.com/symfony/dependency-injection) pokazują, jak sam framework wykorzystuje wewnętrznie dziesiątki takich przebiegów. Grafy wrażliwe na wydajność zyskują na usługach leniwych. Oznaczenie argumentu atrybutem `#[Autowire(lazy: true)]` odracza jego utworzenie do chwili pierwszego wywołania zależności, w oparciu o natywne obiekty leniwe z PHP 8.4, a nie generowaną bibliotekę proxy. ```php // src/Service/ReportBuilder.php namespace App\Service; use App\Pdf\PdfRenderer; use Symfony\Component\DependencyInjection\Attribute\Autowire; final class ReportBuilder { public function __construct( // PdfRenderer is only instantiated on first actual use #[Autowire(lazy: true)] private readonly PdfRenderer $renderer, ) {} public function buildMonthly(): string { // If no method touches $this->renderer, it is never constructed return $this->renderer->render('monthly-report'); } } ``` Natywne obiekty leniwe pojawiły się w PHP 8.4 i zostały szczegółowo omówione w przewodniku o [obiektach leniwych w Symfony 8 i PHP 8.4](/blog/symfony/symfony-8-new-features-php-84-lazy-objects). Zysk to kontener, który pozostaje lekki: obciążający współpracownicy, tacy jak generatory PDF, klienci zewnętrznych SDK czy generatory raportów, nic nie kosztują, dopóki nie uruchomi się ścieżka kodu, która faktycznie ich potrzebuje. ## Pytania rekrutacyjne o DI w Symfony wraz z odpowiedziami Częste pytanie rekrutacyjne o DI w Symfony dotyczy różnicy między autowiringiem a autokonfiguracją. Autowiring rozwiązuje argumenty konstruktora pojedynczej usługi na podstawie typu, natomiast autokonfiguracja stosuje tagi, wywołania metod i ustawienia do usługi na podstawie interfejsów, które ona implementuje. To niezależne przełączniki: usługa może być autowirowana bez autokonfiguracji i odwrotnie. Innym częstym pytaniem jest to, dlaczego kontener rozróżnia usługi publiczne i prywatne. Usługi są domyślnie prywatne, co oznacza, że nie można ich pobrać bezpośrednio z kontenera za pomocą `$container->get()` i można je jedynie wstrzyknąć. Prywatne usługi mogą zostać wbudowane (inline) i usunięte podczas kompilacji, co jest szybsze; usługi publiczne muszą pozostać dostępne do pobrania, więc optymalizator zostawia je nienaruszone. Ręczne pobieranie usług z kontenera jest traktowane jako antywzorzec właśnie dlatego, że niweczy tę optymalizację i ukrywa zależności. Zależności cykliczne to klasyczna pułapka i częste pytanie uzupełniające. Jeśli usługa A potrzebuje B w swoim konstruktorze, a B potrzebuje A, kontener nie może zbudować żadnej z nich i zgłasza cykliczne odwołanie na etapie kompilacji. Standardowe rozwiązania to przerwanie cyklu przez wydzielenie wspólnego zachowania do trzeciej usługi, przełączenie jednej strony na wstrzykiwanie leniwe za pomocą `#[Autowire(lazy: true)]`, aby zależność została rozwiązana dopiero przy pierwszym wywołaniu, lub użycie wstrzykiwania przez setter, dzięki czemu obiekt istnieje, zanim zostanie do niego podłączony współpracownik. Rozpoznanie, dlaczego cykl jest zapachem projektowym (design smell), a nie tylko sposobu jego wyciszenia, jest tym, czego rekruterzy faktycznie słuchają. Rekruterzy sprawdzają również moment rozwiązywania zależności w kontenerze. Ponieważ Symfony kompiluje kontener raz i zapisuje go w pamięci podręcznej, nierozwiązywalna zależność ujawnia się podczas rozgrzewania pamięci podręcznej, a nie w czasie żądania. Ta gwarancja jest bezpośrednim skutkiem modelu kompilacji i jednym z powodów, dla których duże aplikacje Symfony pozostają przewidywalne pod obciążeniem. Szerszy zestaw takich pytań zebrano według komponentów i poziomu trudności w [ścieżce technologicznej Symfony](/technologies/symfony). ## Podsumowanie - Pozwolić kontenerowi pracować: rejestrować klasy w katalogu `src/`, podpowiadać typy zależności w konstruktorze i całkowicie pominąć ręczną konfigurację w YAML. - Sięgać po `#[Autowire]` wyłącznie dla obiektów niebędących usługami, parametrów, zmiennych środowiskowych oraz konkretnych identyfikatorów usług. - Rozwiązywać niejednoznaczność interfejsów za pomocą `#[AsAlias]` dla domyślnej implementacji oraz `#[Autowire(service: ...)]` lub `#[Target]` dla jawnych wyborów. - Napędzać wzorce strategii i wtyczek za pomocą `#[AutoconfigureTag]`, priorytetów `#[AsTaggedItem]` i `#[AutowireIterator]`; przechodzić na `#[AutowireLocator]`, gdy leniwe wyszukiwanie jest lepsze niż zachłanna iteracja. - Odraczać kosztownych współpracowników za pomocą `#[Autowire(lazy: true)]`, który wykorzystuje natywne obiekty leniwe z PHP 8.4. - Trzymać usługi jako prywatne i wstrzykiwane, a nie pobierane, aby kompilator mógł je wbudować i zoptymalizować graf. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/symfony/symfony-dependency-injection-service-container-autowiring-tags