Belajar Dependency Injection Pattern dalam PHP

Ahmad Oriza 25 April 2020

Belajar Dependency Injection Pattern dalam PHP

Konsep

Sebelum masuk pembahasan Dependency Injection pattern, teman-teman harus paham dulu apa arti dependency. Dependency adalah ketergantungan. Dalam prakteknya, sebuah komponen pada program seringkali memiliki ketergantungan terhadap komponen lain. Terdapat dua jenis sifat. Pertama, sebuah komponen dapat sepenuhnya bergantung, tidak bisa hidup tanpa komponen lain. Kedua, komponen dapat berdiri sendiri, dia akan memanggil komponen lain pada saat dibutuhkan saja.

Mari kita lihat contoh lebih nyata, biasanya implementasi pada proyek. Teman-teman terbiasa menggunakan framework? atau baru belajar OOP? contohnya begini :

- Kasus pada Framework. Misalnya kita menggunakan sebuah framework populer. Pastinya sering menggunakan controller? ketika ingin mengolah data, pastinya teman-teman memanggil class model bukan?
nah saat teman-teman menginstansiasi model pada controller, disitulah bukti bahwa controller itu memiliki **dependency**.
- Kasus pada Native OOP PHP. Misalnya kita membuat sebuah class `User.php`. Pada class tersebut teman-teman ingin mengolah database. Tentunya butuh class lain seperti `PDO` bukan?
Nah, berarti disini class `User.php` memiliki dependency ke class `PDO`.

Begitulah cara memahami istilah dependency. Mari kita lanjutkan untuk belajar Dependency Injection (DI) lebih dalam. Seperti biasa, kita akan mulai dari contoh program tanpa pattern. Bagi kalian yang sudah paham, dan hanya ingin lihat implementasi pattern langsung, silahkan skip materi "Praktek tanpa DI".

Praktek Tanpa DI

Misalnya kita dalam sebuah proyek besar, membuat sebuah class khusus untuk menangani report. Kurang lebih seperti ini :

<?php /** * Class Report * * Disini kumpulan kode untuk menangani report. */ class Report { // Mengambil total omzet berdasarkan bulan .. public function getTotalOmzetByMonth() { // Kode-kode query dst echo "Get omzet, return array \n"; } // Mengambil produk paling populer public function getPopularProduct() { // Kode-kode query dst echo "Get popular product, return array \n"; } } $report = new Report; $report->getTotalOmzetByMonth(); $report->getPopularProduct();

Output :

Get omzet, return array Get popular product, return array

Bisa dilihat kita menginstansiasi class kemudian menjalankan kedua buah method yang ada. Pertama, method getTotalOmzetByMonth() untuk mengambil omzet bulanan. Kedua, method getPopularProduct() untuk mengambil data produk paling populer dari Database. Secara default output dari kedua method tersebut adalah array.

Beberapa bulan kemudian, kita ingin membuat fitur baru, yaitu mengexport report dalam format Excel. Kemudian kita mencari-cari library yang cocok dan menerapkannya pada program. Kurang lebih seperti ini :

<?php // Load dependency // Ini hanya contoh! biasanya library/class ini terdapat pada file terpisah, lalu kita masukan dengan require/include // Isinya adalah method untuk produksi file Excel. class PHPExcel { public function export($data) { echo "Exporting data to excel format ..\n"; } } /** * Class Report * * Disini kumpulan kode untuk menangani report. */ class Report { // Mengambil total omzet berdasarkan bulan .. public function getTotalOmzetByMonth() { $data = ''; // Contoh saja, harusnya ada isinya // Panggil excel $PHPExcel = new PHPExcel(); $PHPExcel->export($data); echo "Get omzet, return exported excel path \n"; } // Mengambil produk paling populer public function getPopularProduct() { $data = ''; // Contoh saja, harusnya ada isinya // Panggil excel $PHPExcel = new PHPExcel(); $PHPExcel->export($data); echo "Get popular product, return exported excel path \n"; } } $report = new Report; $report->getTotalOmzetByMonth(); $report->getPopularProduct();

Simple, kita hanya menambahkan class PHPExcel sebagai helper untuk memproduksi excel. Bisa dilihat pada kode. Kita panggil dan instansiasi class Excel pada badan class Report. Kemudian menjalankan perintah export. Tentunya class tersebut hanya khayalan agar teman-teman dapat menjalankan kode ini.

Output kode tersebut sekarang menjadi :

Exporting data to excel format .. Get omzet, return exported excel path Exporting data to excel format .. Get popular product, return exported excel path

Kode tersebut berjalan lancar sesuai scenario yang sudah kita buat. Namun ...

Cara memprogram seperti ini, dengan cara menginjeksi/instansiasi class dependen langsung di badan program relatif tidak dianjurkan. Terdapat beberapa masalah diantaranya :

- Tight Coupled, Class `Report` menjadi sangat bergantung dengan objek konkrit dari class `PHPExcel`. Jika di kemudian hari kita akan berganti library/class, tentunya kita harus coding ulang semua class yang sudah bergantung. Sangat terasa repotnya jika PHPExcel ini sudah menyebar di berbagai class program kita.
- Bad Readability, instansiasi dependen langsung pada class method membuat kode makin sulit dibaca dan dimengerti. Sangat terasa mungkin jika kita bekerja sebagai tim. Rekan kita tidak bisa dengan cepat mengetahui bahwa class tersebut membutuhkan dependen lain.
- Hard to Test, unit testing sulit untuk dilakukan pada class Report. Secara teknis kita tidak bisa membangun mock object.

Nah, kita bisa coba menyelesaikan masalah tersebut dengan Dependency Injection pattern.

Praktek Dengan DI

Mari kita coba implementasi dependency injection. Berikut ini langkah-langkahnya :

- Hapus instansiasi PHPExcel pada setiap method.
- Buat method baru, berupa konstruktor `__constructor` dengan parameter type hinting dari class yang dibutuhkan.
- Buat property untuk menampung object dependen. 
- Pada konstruktor, assign objek dependen yang dipassing, masukan ke property penampung.
- Gunakan property untuk melakukan export excel.

Kurang lebih kodenya jadi seperti ini :

<?php // Load dependency // Ini hanya contoh! biasanya library/class ini terdapat pada file terpisah, lalu kita masukan dengan require/include // Isinya adalah method untuk produksi file Excel. class PHPExcel { public function export($data) { echo "Exporting data to excel format ..\n"; } } /** * Class Report * * Disini kumpulan kode untuk menangani report. */ class Report { /** Excel */ protected $phpexcel; public function __construct(PHPExcel $phpexcel) { // Set phpexcel. $this->phpexcel = $phpexcel; } // Mengambil total omzet berdasarkan bulan .. public function getTotalOmzetByMonth() { $data = ''; // Contoh saja, harusnya ada isinya // Menjalankan export via property. $this->phpexcel->export($data); echo "Get omzet, return exported excel path \n"; } // Mengambil produk paling populer public function getPopularProduct() { $data = ''; // Contoh saja, harusnya ada isinya // Menjalankan export via property. $this->phpexcel->export($data); echo "Get popular product, return exported excel path \n"; } } // Membuat instance report beserta konstruktor parameter instance PHPExcel. $report = new Report(new PHPExcel); // Jalankan method. $report->getTotalOmzetByMonth(); $report->getPopularProduct();

Output kode tersebut masih sama saja dengan sebelumnya. Tapi sekarang class Report menjadi lebih clean. Class Report menjadi lebih independen. Ketika kita butuh PHPExcel, tinggal kita instansiasi objeknya pada konstruktor Report. Dengan begini ketika kita mengganti driver atau library, tinggal mengganti new PHPEXcel saja.

Sebenarnya inti dari DI sangat simple. Yaitu jangan menginstansiasi langsung objek dependen pada class, melainkan memindahkannya menjadi type hinting class.

Service Container

Tahap selanjutnya dari pembahasan DI adalah Service Container. Dengan teknik service container kita dapat menyerahkan management dependency pada komponen terpisah. Komponen ini dapat bertindak cerdas, memilah milih mana dependency dan meng-injectnya secara otomatis pada class yang membutuhkan.

Masih belum terbayang?

Coba lihat kode kita pada latihan sebelumnya :

$report = new Report(new PHPExcel);

Dengan implementasi service container, maka kode akan berubah menjadi hanya seperti ini :

$container = new Container; $report = $container->resolve('Report');

Kita tidak perlu lagi menginstansiasi dependency PHPExcel. Sudah otomatis dihandle oleh method resolve(). Lebih ajaib lagi service container dapat membaca keseluruhan dependency pada Lifecyle program. Contoh :

- Class Report butuh class PHPExcel
- Class PHPExcel butuh class Config.
- Class Config butuh class Loader.
- Dan seterusnya ...

Semua tingkatan kebutuhan class ini dapat otomatis dipenuhi oleh service container. Teknik ini juga biasa disebut dengan autowiring.

Untuk mempelajari service container, teman-teman dapat melihat contoh kode pada repository berikut ini https://github.com/gemblue/SimpleDependencyInjection

Penutup

Demikian materi tentang Dependency Injection. Semoga dapat dipahami dan dipraktekkan. Berikut ini beberapa referensi lanjutan yang bisa dibaca :