Satu baris composer require hanya butuh beberapa detik, tetapi komitmennya berumur tahunan: kode orang lain ikut menentukan kecepatan, keamanan, dan biaya pemeliharaan aplikasi kita. Setiap dependensi membawa permukaan serangan, jadwal rilis, dan preferensi desain pembuatnya — dan semuanya itu menjadi tanggung jawab kita juga. Chapter ini membahas sisi prinsip dari pengelolaan dependensi: kapan menambah, bagaimana menilai kesehatan paket, dan mengapa bergantung pada abstraksi membuat beban itu jauh lebih ringan. Sisi perkakas Composer sendiri sudah kita bahas tuntas pada section Composer dan Dependency Management
.
Prinsip Kebersihan Dependensi #
Ada dua prinsip dasar yang menahan laju penambahan dependensi. Pertama, sesedikit mungkin: setiap paket baru adalah kode yang tidak kita tulis namun kita tanggung — ketika paket itu bocor keamanannya atau berhenti dirawat, kita yang menanggung akibatnya. Kedua, hanya yang terawat baik: paket yang sama fungsinya bisa sangat berbeda nasibnya; yang satu aktif dirilis dan responsif terhadap laporan, yang lain diam bertahun-tahun. Dependensi terbaik sering kali adalah dependensi yang tidak perlu ditulis sama sekali, karena kebutuhannya ternyata cukup ringan untuk kita tulis sendiri.
Kedua prinsip ini tidak anti-paket. Justru sebaliknya: dengan disiplin masuk, paket yang benar-benar kita pakai mendapat perhatian penuh — kita tahu versinya, kita ikuti perkembangannya, dan kita perbaruinya secara teratur. Masalah klasik muncul ketika daftar dependensi tumbuh pasif: ditambah saat butuh, lupa saat tidak, sampai suatu hari upgrade PHP besar terblokir oleh satu paket kecil yang tak ada yang berani menyentuh.
Dependensi Langsung dan Transitif #
Kode berikut memodelkan pohon dependensi sederhana dan memisahkan mana yang kita tulis sendiri di composer.json (dependensi langsung) dan mana yang datang menumpang lewat dependensi kita (dependensi transitif):
<?php
$dependensi = [
"aplikasi-kita" => ["monolog/monolog", "nyholm/psr7"],
"monolog/monolog" => ["psr/log"],
"nyholm/psr7" => ["psr/http-factory", "psr/http-message"],
"psr/http-factory" => ["psr/http-message"],
];
function kumpulkanSeluruhPaket(array $pohon, string $akar = "aplikasi-kita"): array
{
$terlihat = [];
$antrean = $pohon[$akar] ?? [];
while ($antrean !== []) {
$paket = array_shift($antrean);
if (isset($terlihat[$paket])) {
continue;
}
$terlihat[$paket] = true;
foreach ($pohon[$paket] ?? [] as $anak) {
$antrean[] = $anak;
}
}
$daftar = array_keys($terlihat);
sort($daftar);
return $daftar;
}
$langsung = $dependensi["aplikasi-kita"];
$seluruh = kumpulkanSeluruhPaket($dependensi);
$transitif = array_values(array_diff($seluruh, $langsung));
echo "Dependensi langsung (", count($langsung), "):\n";
foreach ($langsung as $paket) {
echo " - ", $paket, "\n";
}
echo "Dependensi transitif (", count($transitif), "):\n";
foreach ($transitif as $paket) {
echo " - ", $paket, "\n";
}Output yang Dihasilkan:
Dependensi langsung (2):
- monolog/monolog
- nyholm/psr7
Dependensi transitif (3):
- psr/http-factory
- psr/http-message
- psr/logMari kita bedah cara kerjanya:
$pohon[$akar] ?? []: penelusuran dimulai dari daftar dependensi langsung aplikasi, yang dijadikan isi awal antrean.while ($antrean !== []): paket diambil satu per satu dari depan antrean; setiap paket yang belum pernah dilihat dicatat, lalu dependensinya dimasukkan ke antrean.isset($terlihat[$paket]): pemeriksaan ini yang mencegah penelusuran berulang —psr/http-messagemuncul dua kali di pohon, tetapi hanya diproses sekali.array_diff($seluruh, $langsung): dari seluruh paket yang terkumpul, dependensi langsung dikeluarkan; sisanya adalah dependensi transitif yang hidup di aplikasi kita tanpa pernah kita sebut namanya.
Pada contoh di atas, dua dependensi langsung membawa tiga dependensi transitif. Pada aplikasi nyata perbandingannya jauh lebih ekstrem: belasan paket langsung bisa membawa ratusan paket transitif. Perintah composer show --tree memperlihatkan pohon seperti ini untuk proyek Anda yang sesungguhnya.
Aturan Dependensi: Bergantung pada Abstraksi #
Cara paling ampuh menekan biaya dependensi bukanlah mengurangi jumlahnya, melainkan mengubah cara kita bergantung padanya. Aturan dependensi menyebut: modul tingkat tinggi sebaiknya bergantung pada abstraksi, bukan pada implementasi paket tertentu. Kode berikut mendefinisikan abstraksi penyimpanan sesi milik aplikasi kita sendiri:
<?php
interface PenyimpananSesi
{
public function simpan(string $kunci, array $data): void;
public function ambil(string $kunci): ?array;
}Dengan interface ini, kode bisnis kita hanya mengenal PenyimpananSesi — ia tidak tahu dan tidak peduli apakah di baliknya berdiri Redis, SQLite, atau array dalam memori. Paket klien Redis menjadi detail lapisan infrastruktur yang bisa ditukar tanpa menyentuh satu baris logika aplikasi. Ketika suatu hari kita migrasi penyimpanan, biayanya berupa satu adapter baru, bukan proyek besar. Kita sudah membahas bentuk penuh prinsip ini pada chapter Dependency Inversion
.
Biaya Kopling #
Setiap dependensi menciptakan kopling — keterikatan nasib aplikasi kita dengan pihak luar. Kopling tidak terasa saat paket baru saja dipasang; kopling terasa saat dunia bergerak: PHP merilis versi mayor, paket menaikkan requirement, atau maintainer menghilang. Tabel berikut memetakan sinyal bahwa biaya kopling mulai menumpuk:
| Sinyal | Mengapa mahal |
|---|---|
| Dependensi jarang diperbarui | pekerjaan upgrade menumpuk sampai terpaksa dikerjakan sekaligus dalam keadaan terburu-buru |
Constraint sangat longgar (*, dev-main) | versi transitif bisa bergeser diam-diam di setiap composer update |
| Paket kecil untuk hal sepele | satu fungsi pembulatan atau format tanggal bisa ditulis sendiri daripada menanggung satu paket penuh |
| Fork lokal yang menggantikan paket upstream | kita kini merawat seluruh paket itu sendiri tanpa dukungan komunitasnya |
Ukuran biaya kopling paling jujur terlihat dari satu pertanyaan: seberapa sakit melepas paket ini? Jika jawabannya “hanya mengganti satu adapter kecil”, kopling sehat. Jika jawabannya “harus menulis ulang lapisan aplikasi”, kopling sudah menjadi utang — dan sebaiknya masuk register technical debt kita.
Vendoring versus Package Manager #
Sebelum era Composer, praktik umum menyalin kode pihak ketiga ke dalam repositori aplikasi — praktik yang disebut vendoring. Tabel berikut membandingkan keduanya:
| Aspek | Vendoring (salin ke repo) | Package manager (Composer) |
|---|---|---|
| Kontrol terhadap kode | penuh — kode ada di tangan kita | tidak ada — kode datang dari sumber upstream |
| Pembaruan | manual, sering tertinggal | satu perintah composer update |
| Jejak audit | sulit tahu bagian mana yang pernah diubah | terkunci di composer.lock yang bisa diperiksa |
| Kontribusi balik ke upstream | patch lokal cenderung menyimpang | perubahan dikirim sebagai pull request |
| Kecocokan untuk proyek kecil solo | kadang cukup | standar industri, tooling lengkap |
Vendoring masih sah dipakai untuk kasus sempit — misalnya satu skrip tunggal yang harus berjalan tanpa composer install. Untuk aplikasi dan library PHP modern, package manager menang mutlak: dependensi dinyatakan sebagai data (composer.json), versi pasti dikunci (composer.lock), dan seluruh ekosistem Packagist
tinggal satu perintah jauhnya. Sejarah dan cara kerja Composer ini kita bahas mulai chapter Apa itu Composer
.
Menilai Kesehatan Paket #
Sebelum menerima paket baru, lakukan pemeriksaan kesehatan sederhana terhadap proyeknya. Empat indikator yang paling informatif: keaktifan rilis (umur rilis terakhir), jumlah maintainer (risiko bus factor), rasio issue terbuka terhadap laju penutupannya, dan dukungan versi PHP yang masih hidup. Kode berikut mengubah indikator-indikator itu menjadi skor pada data tetap:
<?php
function nilaiKesehatanPaket(array $paket): array
{
$skor = 100;
$catatan = [];
if ($paket["hariSejakRilis"] > 365) {
$skor -= 30;
$catatan[] = "rilis terakhir lebih dari setahun lalu";
} elseif ($paket["hariSejakRilis"] > 180) {
$skor -= 15;
$catatan[] = "rilis terakhir lebih dari enam bulan lalu";
}
if ($paket["maintainer"] < 2) {
$skor -= 20;
$catatan[] = "bus factor rendah: maintainer tunggal";
}
if ($paket["issueTerbuka"] / $paket["issueTertutupPerBulan"] > 20) {
$skor -= 25;
$catatan[] = "issue menumpuk dibanding laju penutupan";
}
if (!str_contains($paket["phpDidukung"], "8.3")) {
$skor -= 25;
$catatan[] = "belum mendukung PHP yang masih aktif";
}
$status = match (true) {
$skor >= 80 => "sehat",
$skor >= 50 => "perlu dikaji",
default => "cari alternatif",
};
return ["skor" => $skor, "status" => $status, "catatan" => $catatan === [] ? ["tidak ada catatan"] : $catatan];
}
$paketDikaji = [
[
"nama" => "budi/pustaka-rupiah",
"hariSejakRilis" => 45,
"maintainer" => 3,
"issueTerbuka" => 12,
"issueTertutupPerBulan" => 8,
"phpDidukung" => "^8.1 || ^8.2 || ^8.3 || ^8.4",
],
[
"nama" => "contoh/pdf-bayangan",
"hariSejakRilis" => 420,
"maintainer" => 1,
"issueTerbuka" => 86,
"issueTertutupPerBulan" => 2,
"phpDidukung" => "^7.4 || ^8.0",
],
];
foreach ($paketDikaji as $paket) {
$hasil = nilaiKesehatanPaket($paket);
echo $paket["nama"], ": skor ", $hasil["skor"], " → ", $hasil["status"], "\n";
foreach ($hasil["catatan"] as $baris) {
echo " - ", $baris, "\n";
}
}Output yang Dihasilkan:
budi/pustaka-rupiah: skor 100 → sehat
- tidak ada catatan
contoh/pdf-bayangan: skor 0 → cari alternatif
- rilis terakhir lebih dari setahun lalu
- bus factor rendah: maintainer tunggal
- issue menumpuk dibanding laju penutupan
- belum mendukung PHP yang masih aktifPaket pertama meraih skor penuh: dirilis baru satu setengah bulan lalu, dirawat tiga maintainer, issue terbukanya sebanding dengan laju penutupan, dan mendukung PHP yang masih hidup. Paket kedua menunjukkan pola sebaliknya sekaligus bagaimana catatan menjelaskan jatuhnya skor — setiap penalti membawa alasannya, sehingga keputusan “cari alternatif” bisa dibedah dan dibantah bila ada konteks lain. Skor seperti ini adalah alat bantu diskusi, bukan pengganti pertimbangan: paket kecil sehat yang ditulis satu maintainer tekun kadang tetap lebih baik daripada paket ramai yang penuh drama.
Kebiasaan Audit #
Penilaian kesehatan tidak berhenti saat paket baru masuk; kondisi paket bisa berubah setelah kita memasangnya. Karena itu jadikan audit sebagai kebiasaan berkala: jalankan composer audit untuk memindai kerentanan yang sudah diketahui publik, dan composer outdated untuk melihat seberapa jauh versi terpasang tertinggal dari yang tersedia. Kedua perintah ini paling berguna ketika dijalankan otomatis di CI, sehingga temuan datang sebagai laporan rutin, bukan kejutan di tengah insiden. Proses lengkapnya — termasuk menafsirkan advisori keamanan — sudah kita bahas pada chapter Dependency Auditing
.
Ringkasan #
Pengelolaan dependensi yang disiplin berdiri di atas tiga kebiasaan: masuk selektif (sesedikit mungkin, hanya yang terawat), bergantung pada abstraksi (detail paket tertahan di lapisan infrastruktur), dan memantau terus-menerus (audit serta pembaruan berkala, bukan setahun sekali). Pohon dependensi transitif adalah bagian dari aplikasi kita, dan kesehatannya layak diperlakukan seperti kesehatan kode sendiri. Namun ada satu janji yang belum kita bahas: ketika aplikasi atau library kita sendiri yang tumbuh dan berubah, bagaimana caranya berubah tanpa memutus semua orang yang bergantung pada kita? Itulah topik penutup section ini, backward compatibility.
Referensi #
- Dokumentasi Composer
— perintah
show --tree,audit, danoutdateddalam praktiknya. - Versi dan Constraint Composer — cara membaca dan menulis constraint dependensi.
- Packagist — repositori paket PHP utama untuk memeriksa statistik unduhan dan rilis.
- PHP The Right Way — praktik dependensi dan manajemen paket di ekosistem PHP.