Mengupas service container: cara service dibuat dan disuntikkan, autowiring dengan konfigurasi services.yaml, _defaults, binding interface ke implementasi, parameter & env vars, lazy loading, serta perintah debug:container dan debug:autowiring untuk menelusuri graph.

Sejak episode 2 kita menyebut Dependency Injection Container sebagai fondasi Symfony. Episode 8 mengupasnya tuntas: apa itu service, bagaimana container membuatnya, dan mengapa arsitektur ini mengubah cara kalian menulis kode.
Mengapa ini topik yang paling penting untuk dikuasai? Karena di sinilah letak perbedaan terbesar antara "menulis PHP" dan "menulis Symfony". Aplikasi yang sehat dibangun dari banyak service kecil yang tersusun — dan container yang memastikan susunan itu benar, konsisten, dan mudah di-test. Begitu kalian memahami DI, membaca kode framework (dan iklan lowongan kerja Symfony) terasa jauh lebih mudah.
Service adalah objek yang melakukan tugas spesifik, dibuat sekali (singleton dalam satu request) dan dipakai bersama. Contoh: Mailer, UserRepository, HttpClient, LoggerInterface. Berlawanan dengan "new object di dalam method", service hidup di container dan di-inject ke pemakainya:
<?php
namespace App\Service;
use Psr\Log\LoggerInterface;
class ArticlePublisher
{
public function __construct(
private readonly LoggerInterface $logger,
) {
}
public function publish(array $data): void
{
// logika penerbitan artikel...
$this->logger->info('Artikel diterbitkan', $data);
}
}Perhatikan: LoggerInterface masuk lewat constructor — class ini tidak pernah membuat dependency-nya sendiri. Itulah ciri khas DI: dependencies diberikan, bukan dicari.
Symfony 4+ mengaktifkan autowiring secara default. Container melihat type-hint constructor dan otomatis mencari service yang cocok. Kalian tidak perlu mendaftarkan App\Service\ArticlePublisher secara manual — cukup biarkan di namespace App\:
services:
_defaults:
autowire: true
autoconfigure: true
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'Penjelasan per baris:
| Kunci | Fungsi |
|---|---|
_defaults.autowire | Semua service di file ini di-inject otomatis |
_defaults.autoconfigure | Service otomatis terdaftar sebagai tag (mis. event subscriber) |
App\ resource ../src/ | Container memindai semua class di src/ sebagai service |
exclude | Entity dan Kernel bukan service — mereka objek data |
Karena itu di episode 6 kita bisa menulis ArticleRepository $repo di controller tanpa mendaftarkan apa pun: container menemukannya, membangunnya, dan menyuntikkannya.
Autowiring hanya berhasil jika type-hint unambiguous — satu interface punya satu implementasi. Saat lebih dari satu implementasi, kalian harus memberi tahu container yang mana:
interface PaymentGatewayInterface
{
public function charge(float $amount): void;
}
class StripeGateway implements PaymentGatewayInterface {}
class MidtransGateway implements PaymentGatewayInterface {}Tanpa binding, container tidak tahu harus menyuntikkan yang mana:
services:
App\Service\StripeGateway: ~
App\Service\MidtransGateway: ~
App\Service\PaymentGatewayInterface:
alias: App\Service\StripeGatewaySekarang PaymentGatewayInterface $gateway di mana pun akan menerima StripeGateway. Ganti implementasi cukup di satu tempat — inilah decoupling: pemakai tidak peduli provider pembayaran, hanya interface-nya.
Service kadang butuh nilai konfigurasi. Dua sumber: parameter (YAML) dan env var (.env):
parameters:
blog.items_per_page: 10
services:
App\Service\ArticlePublisher:
arguments:
$itemsPerPage: '%blog.items_per_page%'
$adminEmail: '%env(ADMIN_EMAIL)%'Parameter memakai sintaks %nama%; env var memakai %env(VAR)%. Untuk value yang sering berubah antar environment, selalu pilih env var (dibahas penuh di episode 16) — parameter cocok untuk nilai yang memang statis.
Beberapa service mahal dibuat (koneksi ke API, load konfigurasi besar) padahal jarang dipakai. Container bisa menunda pembuatannya sampai benar-benar diakses:
services:
App\Service\HeavyAnalyzer:
lazy: trueDengan lazy: true, container membuat proxy yang menginstansiasi objek asli hanya saat method pertama kali dipanggil. Ini memangkas waktu request untuk aplikasi dengan banyak service berat.
Kekuatan Symfony adalah introspectability — semua bisa diperiksa:
php bin/console debug:container # daftar semua service
php bin/console debug:container --show-arguments # detail konstruktor
php bin/console debug:autowiring # cari class yang bisa di-inject
php bin/console debug:autowiring --search Mail # filter namaSaat autowiring gagal, error Symfony cukup jelas: ia menyebutkan service yang gagal, argumen yang tidak bisa diselesaikan, dan daftar service yang cocok dengan type-hint. Ini salah satu alasan mengapa kalian tidak perlu "menebak-nebak" — container selalu bisa memberi tahu graph-nya.
Warning
Jangan pernah memanggil new pada service di dalam controller/method, dan hindari $this->container->get() secara liar. Keduanya membuat service tidak bisa di-test dan merusak jejak dependency. Jika suatu class butuh service, tulis type-hint di constructor atau argument method — container yang bekerja.
shared; jika butuh instance baru setiap pemakaian, atur shared: false.Pada episode 8 ini, kalian telah menguasai mekanisme inti Symfony.
Inti yang harus dibawa pulang:
_defaults mengaktifkannya global.%x% untuk nilai statis; %env(X)% untuk nilai lingkungan.lazy: true menunda pembuatan service berat; debug:container untuk inspeksi.Di episode 9 selanjutnya kita keluar dari web dan masuk ke CLI: Console & CLI Commands — membuat command dengan arguments & options, input/output interaktif (table, progress bar), exit code, dan praktik CLI tooling untuk import data & cron. Sampai jumpa di episode 9!