Aplikasi PHP yang sesungguhnya jarang ditulis dari nol. Fitur seperti pencatatan aktivitas (logging), pengiriman email, pembuatan berkas PDF, atau konsumsi API pihak ketiga biasanya diserahkan kepada pustaka (library) yang sudah teruji oleh ribuan pengembang lain. Pertanyaannya: bagaimana cara membawa pustaka-pustaka itu ke dalam proyek, memastikan versinya saling cocok, lalu memuat kodenya tanpa ribet? Composer adalah jawaban standar ekosistem PHP untuk ketiga persoalan tersebut. Pada chapter ini kita memahami dulu masalah apa yang sebenarnya diselesaikan Composer, karena seluruh perintah pada chapter-chapter berikutnya baru masuk akal setelah masalahnya jelas.

Masalah Sebelum Composer #

Bayangkan menggarap proyek toko online pada era sebelum Composer ada. Anda membutuhkan pustaka untuk membuat berkas PDF, mengirim email, dan melakukan permintaan HTTP. Untuk setiap kebutuhan tersebut, Anda membuka situs web masing-masing pustaka, mengunduh arsip zip, mengekstraknya ke folder lib/ di dalam proyek, lalu memuat berkas-berkasnya satu per satu:

php
<?php
// Zaman sebelum Composer: memuat pustaka satu per satu di setiap skrip
require_once 'lib/pdf/fpdf.php';
require_once 'lib/pdf/fpdi.php';
require_once 'lib/mail/phpmailer.php';
require_once 'lib/mail/smtp.php';
require_once 'lib/http/guzzle/functions.php';

Urutan pemanggilan require_once harus benar karena satu pustaka bisa bergantung pada berkas pustaka lain. Ketika versi baru dirilis, Anda mengunduh ulang secara manual dan menimpa folder lama, tanpa cara mudah mengetahui apa saja yang berubah dan apakah kode Anda masih kompatibel. Masalah memuncak ketika beberapa proyek berbagi satu folder pustaka yang sama: proyek A menuntut versi 1.x sementara proyek B membutuhkan versi 2.x, dan keduanya tidak bisa hidup berdampingan.

PHP sempat memiliki solusi bernama PEAR — repositori dan installer pustaka resmi yang memasang paket secara global ke satu folder bersama milik server. PEAR menghilangkan kerja manual mengunduh, tetapi satu folder global justru memperparah konflik versi antar proyek, dan katalog paketnya tumbuh lambat. Ekosistem membutuhkan pendekatan yang berbeda: dependensi dikelola per proyek, dinyatakan dalam sebuah berkas deklarasi, dan versinya diselesaikan secara otomatis.

Dependensi dan Masalah Versinya #

Dependensi (dependency) adalah pustaka eksternal yang dibutuhkan kode kita agar berfungsi. Jika aplikasi toko memakai monolog untuk logging, maka monolog adalah dependensi aplikasi tersebut. Kompleksitasnya, dependensi juga memiliki dependensi sendiri yang disebut dependensi transitive: monolog membutuhkan psr/log sebagai antarmuka logging standar. Sebuah proyek modern dengan dua puluh paket langsung bisa membawa lebih dari seratus paket transitive.

Konflik muncul ketika dua paket menuntut versi yang tidak bertetangga. Misalnya, pustaka pembayaran yang Anda pakai masih mengharuskan guzzlehttp/guzzle versi 6, sementara pustaka pengiriman barang terbaru hanya mendukung versi 7. Sebelum Composer, rekonsiliasi seperti ini dikerjakan dengan tangan: mengganti paket, menambal kode, atau menahan pembaruan. Composer menyelesaikannya dengan resolver — algoritma yang membaca seluruh kendala versi lalu memilih satu kombinasi yang saling cocok, atau melaporkan konflik secara jelas apabila tidak ada solusi.

Alur Kerja Composer #

Interaksi kita dengan Composer berporos pada satu berkas deklarasi bernama composer.json. Di dalam berkas tersebut kita cukup menyatakan pustaka apa yang dibutuhkan beserta rentang versi yang bisa diterima:

json
{
    "require": {
        "monolog/monolog": "^3.7"
    }
}

Alur lengkapnya terdiri atas empat langkah:

  1. Tulis daftar dependensi beserta kendala versinya di composer.json.
  2. Jalankan Composer; resolver menghitung kombinasi versi yang saling cocok untuk semua paket, termasuk dependensi transitive.
  3. Composer mengunduh setiap paket dan menaruhnya di folder vendor/ milik proyek — tidak ada lagi folder global yang diperebutkan antar proyek.
  4. Composer menghasilkan autoloader di vendor/autoload.php sehingga seluruh class dari semua pustaka dapat dipakai tanpa satu pun require manual.

Karena composer.json hanyalah JSON, kita bisa membacanya seperti data biasa dari dalam PHP — dan pada dasarnya itulah yang dilakukan Composer sendiri setiap kali dijalankan:

php
<?php
$manifest = '{"monolog/monolog": "^3.7", "psr/log": "^3.0"}';
$dependensi = json_decode($manifest, true);

foreach ($dependensi as $paket => $kendala) {
    echo "$paket -> versi $kendala\n";
}

Output yang Dihasilkan:

text
monolog/monolog -> versi ^3.7
psr/log -> versi ^3.0

Mari kita telaah kode di atas:

  1. Baris 2: string JSON berisi dua entri dependensi didefinisikan sebagai contoh miniatur dari isi composer.json.
  2. Baris 3: json_decode() dengan argumen kedua true mengubah string JSON menjadi array asosiatif PHP — kunci berupa nama paket, nilai berupa kendala versi.
  3. Baris 5-7: perulangan foreach mencetak setiap pasangan paket dan kendala. Tanda ^3.7 berarti “versi 3.7 ke atas, selama major version tidak berubah” — pembahasan tuntasnya ada pada chapter version constraints .

Registry tempat Composer mencari paket bernama Packagist (packagist.org). Nama paket selalu berformat vendor/nama, misalnya monolog/monolog atau psr/log, sehingga tidak ada dua paket berbeda yang bertabrakan nama. Packagist mengindeks jutaan paket dan menjadi tujuan penerbitan bagi hampir semua pustaka PHP modern.

Autoloader sebagai Bonus Terbesar #

Satu peran Composer yang sering luput: selain dependency manager, Composer juga menghasilkan autoloader. Sebelum membahas autoloader buatan Composer, kita lihat dulu mekanisme dasar yang disediakan PHP. Fungsi spl_autoload_register() mendaftarkan sebuah callback yang dipanggil oleh interpreter setiap kali class yang belum dikenal dipakai:

php
<?php
spl_autoload_register(function (string $class): void {
    echo "Autoloader dipanggil untuk: $class\n";
});

var_dump(class_exists('DateTimeImmutable'));
var_dump(class_exists('Monolog\Logger'));

Output yang Dihasilkan:

text
bool(true)
Autoloader dipanggil untuk: Monolog\Logger
bool(false)

Mari kita bedah perilakunya baris per baris:

  1. Baris 2-4: sebuah closure didaftarkan sebagai autoloader; closure tersebut hanya mencetak nama class yang sedang dicari.
  2. Baris 6: class_exists('DateTimeImmutable') mengembalikan bool(true) tanpa memanggil autoloader, karena class internal PHP sudah terdefinisi sejak interpreter menyala.
  3. Baris 7: Monolog\Logger tidak dikenal, sehingga interpreter memanggil autoloader terlebih dahulu — terlihat dari baris Autoloader dipanggil untuk: Monolog\Logger. Karena autoloader kita memang tidak memuat apa pun, class tetap tidak ditemukan dan hasilnya bool(false).

Composer memakai mekanisme yang persis sama. Berkas vendor/autoload.php yang dihasilkan Composer mendaftarkan autoloader yang tahu persis lokasi setiap class di seluruh paket dalam folder vendor/. Setelah satu baris require 'vendor/autoload.php';, class seperti Monolog\Logger langsung dapat dipakai tanpa memikirkan berkas definisinya berada di mana.

Fondasi Ekosistem PHP Modern #

Composer kini menjadi fondasi seluruh ekosistem PHP. Framework besar seperti Laravel dan Symfony didistribusikan sebagai kumpulan paket Composer; komponen Symfony dapat dicampur ke proyek apa pun tanpa memakai framework penuh; perkakas kualitas seperti PHPUnit, PHPStan, dan Psalm dipasang sebagai paket. Keterampilan membaca dan menulis konfigurasi Composer dengan demikian menjadi prasyarat untuk hampir semua pekerjaan pengembangan PHP modern. Langkah pertama kita tentu saja memasang Composer di mesin sendiri — topik yang dibahas tuntas pada chapter berikutnya.

Referensi #