# Dependency Injection Symfony: Service Container, Autowiring, dan Tag di 2026 > Ulasan mendalam tentang dependency injection Symfony: service container, autowiring, atribut #[Autowire], binding interface, tagged service dengan #[AutowireIterator], compiler pass, dan lazy service di Symfony 7.4 dan 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 --- Dependency injection adalah tulang punggung setiap aplikasi Symfony: service container membuat instance, mengonfigurasi, dan menyambungkan objek satu sama lain sehingga sebuah kelas cukup mendeklarasikan apa yang dibutuhkannya, bukan membangunnya sendiri. Pada Symfony 7.4 dan 8.0, autowiring, atribut PHP, dan tagged service telah memangkas konfigurasi eksplisit hingga nyaris nol sambil menjaga container tetap teresolusi penuh dan dapat diprediksi pada waktu kompilasi. Memahami cara resolusi itu bekerja membedakan developer yang melawan container dari mereka yang membiarkan container bekerja untuk mereka. > **Apa itu dependency injection di Symfony?** > > Dependency injection di Symfony adalah pola di mana service container mengoper dependency sebuah objek melalui konstruktornya, bukan membiarkan objek itu membuatnya sendiri. Autowiring meresolusi setiap argumen ber-type-hint ke service yang cocok secara otomatis, sehingga sebagian besar kelas sama sekali tidak butuh konfigurasi manual. ## Bagaimana Service Container Symfony Meresolusi Dependency Service container adalah kelas PHP terkompilasi yang tahu cara membangun setiap service di dalam aplikasi. Selama fase kompilasi container, Symfony memindai definisi kelas, membaca signature konstruktor, dan menghasilkan factory yang di-cache. Saat runtime, meminta sebuah service akan mengembalikan instance bersama yang dibangun secara lazy. Karena penyambungan terjadi pada waktu kompilasi, dependency yang salah konfigurasi akan gagal ketika cache dibangun, bukan ketika request menghantam production. Sebuah service pada dasarnya hanyalah kelas yang menjalankan suatu tugas dan dikelola oleh container. Berkas bawaan `config/services.yaml` mendaftarkan semua yang ada di bawah `src/` sebagai service dengan autowiring dan autoconfiguration aktif, itulah alasan kelas biasa langsung berfungsi tanpa pengaturan tambahan. ```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' ``` Dengan default tersebut, sebuah kelas meminta kolaboratornya lewat konstruktor dan container yang menyediakannya. Tanpa factory, tanpa `new`, tanpa daftar argumen manual. ```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]); } } ``` Container membuat instance `SlackNotifier` satu kali, meng-inject HTTP client dan logger bersama, lalu menggunakan ulang instance itu di mana pun ia diminta. Perilaku shared-by-default inilah yang membuat container cepat sekaligus hemat memori. Model lengkapnya didokumentasikan pada [panduan resmi service container Symfony](https://symfony.com/doc/current/service_container.html), yang dibangun di atas standar [antarmuka container PSR-11](https://www.php-fig.org/psr/psr-11/) yang dipakai bersama di seluruh ekosistem PHP. Sharing bisa dimatikan ketika instance baru dibutuhkan per request, misalnya sebuah builder stateful yang mengakumulasi data. Menetapkan `shared: false` pada sebuah definisi mengubahnya menjadi factory yang mengembalikan objek baru setiap kali di-inject. Ini jarang terjadi dalam praktik, karena mayoritas service adalah kolaborator stateless, tetapi mengetahui bahwa default ini ada dan cara menimpanya jadi penting ketika sebuah bug ternyata berujung pada dua konsumen yang tanpa sengaja memutasi objek yang sama. ## Autowiring Symfony: Type Hint dan Atribut Autowire Autowiring Symfony meresolusi argumen konstruktor berdasarkan deklarasi tipenya. Ketika sebuah argumen di-type-hint dengan kelas atau interface yang memetakan tepat ke satu service, container meng-injectnya tanpa konfigurasi apa pun. Autowiring hanya buntu untuk nilai yang bukan service: skalar, environment variable, dan parameter. Atribut `#[Autowire]` menutup celah itu langsung pada argumennya, menjaga penyambungan tetap berdampingan dengan kode yang menggunakannya. ```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, ) {} } ``` Setiap argumen mendeklarasikan sumbernya sendiri: sebuah parameter, environment variable, atau service bernama. Atribut ini juga menerima argumen `expression:` untuk nilai Expression Language, yang berguna ketika sebuah dependency berasal dari komputasi runtime alih-alih definisi statis. Menaruh metadata ini pada konstruktor berarti pembaca langsung melihat persis dari mana setiap nilai berasal tanpa perlu membuka berkas YAML terpisah. Referensi atributnya ada di [dokumentasi autowiring Symfony](https://symfony.com/doc/current/service_container/autowiring.html). > **Type-hint interface, bukan kelas konkret** > > Meng-autowire kelas konkret seperti `S3Storage` mengikat setiap konsumen ke implementasi itu dan membuat penggantiannya dalam pengujian menyakitkan. Type-hint interfacenya saja dan biarkan `#[AsAlias]` atau `#[Autowire(service: ...)]` yang menentukan service konkretnya. Meng-inject seluruh container untuk mengambil service secara manual mementahkan pemeriksaan waktu kompilasi dan justru merupakan anti-pola yang keberadaan autowiring dimaksudkan untuk menghapusnya. ## Mengikat Interface Ketika Ada Banyak Implementasi Autowiring terhadap sebuah interface bekerja mulus sampai dua kelas mengimplementasikannya. Pada titik itu container tidak dapat menebak mana yang harus di-inject dan melempar error ambiguitas pada waktu kompilasi. Dua atribut menyelesaikan ini: `#[AsAlias]` menandai satu implementasi sebagai default untuk interface tersebut, dan `#[Autowire(service: ...)]` menyematkan implementasi spesifik pada satu argumen tertentu. ```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, ) {} } ``` Atribut `#[Target]` menawarkan opsi ketiga: ia merujuk sebuah implementasi berdasarkan nama alias autowiring-nya, memisahkan nama variabel argumen dari proses resolusinya. Memilih di antara ketiganya bergantung pada niat. Gunakan `#[AsAlias]` ketika satu implementasi memang benar-benar default di seluruh basis kode, dan `#[Autowire(service: ...)]` atau `#[Target]` ketika satu konsumen membutuhkan varian non-default. Pola ini muncul terus-menerus dalam aplikasi production, dan menjadi topik favorit di [modul wawancara dependency injection Symfony](/technologies/symfony/interview-questions/dependency-injection). ## Tag Service Symfony dan Kolektor AutowireIterator Tag adalah cara Symfony mengelompokkan service terkait agar service lain dapat mengonsumsi semuanya sekaligus. Ini memberdayakan pola strategy, chain-of-responsibility, dan plugin yang muncul di seluruh framework itu sendiri: ekstensi form type, ekstensi Twig, dan event subscriber semuanya digerakkan oleh tag. Ketimbang memberi tag secara manual, `#[AutoconfigureTag]` yang ditempatkan pada sebuah interface memberi tahu container untuk memberi tag setiap kelas yang mengimplementasikannya secara otomatis. ```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; } ``` Masing-masing implementasi mengatur posisinya dalam koleksi dengan `#[AsTaggedItem]`, yang menetapkan prioritas (nilai lebih tinggi berjalan lebih dulu) dan indeks string opsional untuk akses berbasis kunci. ```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)); } } ``` Service konsumen mengumpulkan setiap exporter ber-tag ke dalam sebuah `iterable` terurut menggunakan `#[AutowireIterator]`. Ia lalu menelusuri koleksi itu dan mendelegasikan ke strategi pertama yang mendukung format yang diminta. ```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"); } } ``` Ketika koleksinya besar atau mahal untuk dibangun, `#[AutowireLocator]` adalah alternatif lazy: ia meng-inject container PSR-11 yang membuat instance service ber-tag hanya ketika service itu benar-benar diambil berdasarkan kunci. Perbedaan antara iterasi eager dan pencarian lazy itu penting untuk service yang memegang koneksi atau state berat. ## Autoconfiguration, Compiler Pass, dan Lazy Service Autoconfiguration adalah mekanisme yang membaca atribut seperti `#[AutoconfigureTag]` dan `#[AsTaggedItem]` lalu menerapkannya selama kompilasi. Itulah alasan mengimplementasikan `EventSubscriberInterface` sudah cukup untuk mendaftarkan sebuah event subscriber tanpa konfigurasi sama sekali. Untuk transformasi yang tidak dapat diungkapkan oleh atribut, sebuah compiler pass mengait langsung ke proses build container dan memutasi definisi sebelum cache ditulis. Kode sumber komponen `DependencyInjection` di [GitHub](https://github.com/symfony/dependency-injection) memperlihatkan bagaimana framework itu sendiri menggunakan puluhan pass semacam ini secara internal. Graf yang sensitif terhadap performa diuntungkan oleh lazy service. Menandai sebuah argumen dengan `#[Autowire(lazy: true)]` menunda pembuatan instance hingga dependency itu pertama kali dipanggil, ditopang oleh native lazy object PHP 8.4 alih-alih library proxy yang di-generate. ```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'); } } ``` Native lazy object hadir bersama PHP 8.4 dan dibahas mendalam pada panduan tentang [Symfony 8 dan lazy object PHP 8.4](/blog/symfony/symfony-8-new-features-php-84-lazy-objects). Hasilnya adalah container yang tetap ramping: kolaborator berat seperti PDF renderer, client SDK pihak ketiga, atau generator laporan tidak memakan biaya apa pun sampai jalur kode yang membutuhkannya benar-benar berjalan. ## Pertanyaan dan Jawaban Wawancara DI Symfony Pertanyaan wawancara DI Symfony yang umum menanyakan perbedaan antara autowiring dan autoconfiguration. Autowiring meresolusi argumen konstruktor sebuah service berdasarkan tipe, sementara autoconfiguration menerapkan tag, pemanggilan method, dan pengaturan pada sebuah service berdasarkan interface yang diimplementasikannya. Keduanya adalah sakelar yang independen: sebuah service dapat di-autowire tanpa di-autoconfigure, dan sebaliknya. Pertanyaan lain yang sering muncul adalah mengapa container membedakan service public dan private. Service bersifat private secara default, artinya ia tidak dapat diambil langsung dari container dengan `$container->get()` dan hanya dapat di-inject. Service private dapat di-inline dan dioptimalkan hingga hilang selama kompilasi, yang lebih cepat; service public harus tetap dapat diambil, sehingga optimizer membiarkannya utuh. Mengambil service secara manual dari container dianggap anti-pola justru karena ia mementahkan optimasi ini dan menyembunyikan dependency. Circular dependency adalah jebakan klasik sekaligus pertanyaan lanjutan yang sering. Jika service A membutuhkan B di konstruktornya dan B membutuhkan A, container tidak dapat membangun keduanya dan melaporkan referensi melingkar pada waktu kompilasi. Perbaikan standarnya adalah memutus siklus dengan mengekstrak perilaku bersama ke service ketiga, mengalihkan salah satu sisi ke injeksi lazy dengan `#[Autowire(lazy: true)]` agar dependency diresolusi hanya pada panggilan pertama, atau memakai setter injection sehingga objek sudah ada sebelum kolaboratornya disambungkan. Menyadari mengapa siklus itu adalah gejala desain yang buruk, bukan sekadar cara membungkamnya, adalah yang benar-benar didengarkan pewawancara. Pewawancara juga menggali soal waktu resolusi container. Karena Symfony mengompilasi container sekali dan meng-cachenya, dependency yang tidak dapat diresolusi muncul saat cache warmup alih-alih saat request. Jaminan itu adalah konsekuensi langsung dari model waktu kompilasi dan menjadi salah satu alasan aplikasi Symfony berskala besar tetap dapat diprediksi di bawah beban. Untuk kumpulan pertanyaan yang lebih luas, [jalur teknologi Symfony](/technologies/symfony) mengelompokkannya berdasarkan komponen dan tingkat kesulitan. ## Kesimpulan - Biarkan container bekerja: daftarkan kelas di bawah `src/`, type-hint dependency di konstruktor, dan lewati penyambungan YAML manual sepenuhnya. - Gunakan `#[Autowire]` hanya untuk non-service, parameter, environment variable, dan service id spesifik. - Selesaikan ambiguitas interface dengan `#[AsAlias]` untuk default dan `#[Autowire(service: ...)]` atau `#[Target]` untuk pilihan eksplisit. - Gerakkan pola strategy dan plugin dengan `#[AutoconfigureTag]`, prioritas `#[AsTaggedItem]`, dan `#[AutowireIterator]`; beralih ke `#[AutowireLocator]` ketika pencarian lazy lebih disukai ketimbang iterasi eager. - Tunda kolaborator mahal dengan `#[Autowire(lazy: true)]`, yang memakai native lazy object PHP 8.4. - Jaga service tetap private dan di-inject alih-alih diambil, agar compiler dapat meng-inline dan mengoptimalkan grafnya. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/id/blog/symfony/symfony-dependency-injection-service-container-autowiring-tags