Chapter Dependency Auditing mengunci keamanan rantai pasok publik: dependensi Packagist diaudit, diperbarui, dan dijaga di pipeline. Namun tidak semua kode boleh hidup di Packagist — tempat itu hanya menampung paket publik. Kode milik perusahaan seperti integrasi payment gateway internal, helper lintas tim, atau modul khusus satu klien tetap perlu ergonomi Composer yang sama: composer require, composer.lock, hingga composer audit. Pada chapter ini kita pelajari bagaimana Composer menemukan paket dari sumber selain Packagist: konfigurasi repositories, autentikasi yang tidak membocorkan kredensial, path repository yang akan kita coba langsung, hingga Satis dan Packagist.com untuk skala organisasi.

Mengapa Ada Paket Privat #

Alasan paling umum adalah kepemilikan kode. Library yang memuat logika bisnis, rumus harga, atau kredensial infrastruktur tidak boleh dipublikasikan ke registri publik — sekali terunggah, praktis mustahil ditarik kembali. Alasan kedua adalah penghematan duplikasi: tim pembayaran dan tim laporan sama-sama butuh formatter rupiah, dan menyalin-tempel berkas antar proyek akan menciptakan salinan-salinan yang perlahan berbeda. Alasan ketiga adalah distribusi terkendali: vendor eksternal kadang mengirim paket dalam bentuk zip yang hanya boleh dipakai pelanggan tertentu.

Composer tidak membedakan paket publik dan privat — yang ia butuhkan hanyalah sumber yang bisa dibaca. Tugas kita adalah mendaftarkan sumber tersebut di composer.json, lalu memastikan kredensial aksesnya tersedia dengan aman. Dari sudut pandang resolusi dependensi, paket privat dari repositori git perusahaan diperlakukan persis seperti paket publik dari Packagist: kendala versi, lock file, dan audit bekerja dengan cara yang sama.

Composer Mencari Paket dari Mana #

Secara default Composer membaca Packagist.org sebagai sumber pertama. Kunci repositories menambahkan sumber-sumber lain, dan Composer memeriksa sumber yang terdaftar lebih dulu sebelum jatuh ke Packagist. Karena composer.json adalah JSON biasa, kita bisa membedah strukturnya langsung dengan json_decode():

php
<?php
$composerJson = <<<'JSON'
{
    "repositories": [
        {"type": "path", "url": "../util-rupiah"},
        {"type": "vcs", "url": "[email protected]:budi/api-client.git"},
        {"type": "artifact", "url": "dist/zip/"}
    ],
    "require": {
        "budi/util-rupiah": "@dev",
        "budi/api-client": "^1.0"
    }
}
JSON;

$config = json_decode($composerJson, true);

echo "Sumber paket yang terdaftar:\n";
foreach ($config["repositories"] as $repo) {
    printf("- %-9s %s\n", $repo["type"], $repo["url"]);
}

echo "\nKendala versi:\n";
foreach ($config["require"] as $paket => $kendala) {
    echo "- $paket => $kendala\n";
}

Output yang Dihasilkan:

text
Sumber paket yang terdaftar:
- path      ../util-rupiah
- vcs       [email protected]:budi/api-client.git
- artifact  dist/zip/

Kendala versi:
- budi/util-rupiah => @dev
- budi/api-client => ^1.0

Setiap entri repositories memuat type dan url, dan empat tipe inilah yang akan kita gunakan sepanjang chapter:

TipeSumber paketCocok untuk
vcsrepositori Git, Mercurial, atau SVNsatu sampai beberapa paket internal berbasis git
pathfolder lokal di komputer yang samapengembangan monorepo dan library sendiri
composerserver dengan berkas packages.jsonSatis dan Packagist.com privat
artifactfolder berisi berkas .zipvendor yang hanya mengirim paket jadi

VCS Repositori untuk Kode Git Privat #

Bentuk paling umum: paket privat hidup sebagai repositori git biasa di GitHub, GitLab, atau server git perusahaan. Mendaftarkannya hanya butuh satu entri:

json
{
    "repositories": [
        {
            "type": "vcs",
            "url": "[email protected]:budi/api-client.git"
        }
    ],
    "require": {
        "budi/api-client": "^1.0"
    }
}

Saat resolusi berjalan, Composer melakukan tiga hal terhadap URL tersebut: mengkloning repositori, membaca composer.json di akarnya, lalu memindai tag dan branch sebagai daftar versi. Tag v1.2.0 menjadi versi 1.2.0 yang bisa Anda kendalakan dengan notasi dari chapter Version Constraints , sedangkan branch main menjadi versi dev-main. Proses pemindaian ini dijalankan sekali lalu di-cache, sehingga resolusi berikutnya tetap cepat. Untuk repositori privat, langkah kloning itulah yang menuntut kredensial — pembahasannya tepat di bagian berikutnya.

Autentikasi Tanpa Membocorkan Kredensial #

Kredensial tidak pernah ditulis di composer.json — berkas itu terkomit ke repositori dan dibaca seluruh tim. Composer menyediakan tempat khusus: berkas auth.json di sebelah composer.json, atau variabel lingkungan COMPOSER_AUTH yang berisi JSON dengan struktur sama. Dua mekanisme autentikasi yang paling sering dipakai:

json
{
    "github-oauth": {
        "github.com": "YOUR_GITHUB_TOKEN"
    },
    "http-basic": {
        "git.example.com": {
            "username": "budi",
            "password": "YOUR_PASSWORD"
        }
    }
}

Kunci github-oauth menyimpan personal access token untuk GitHub — pilihan yang disarankan karena token bisa dibatasi haknya dan dicabut kapan saja. Kunci http-basic menyimpan pasangan username-password untuk server git yang memakai autentikasi dasar, misalnya GitLab yang di-host sendiri. Struktur berkas ini mudah diperiksa secara programatik:

php
<?php
$authJson = <<<'JSON'
{
    "github-oauth": {
        "github.com": "YOUR_GITHUB_TOKEN"
    },
    "http-basic": {
        "git.example.com": {
            "username": "budi",
            "password": "YOUR_PASSWORD"
        }
    }
}
JSON;

$auth = json_decode($authJson, true);

foreach ($auth["http-basic"] as $host => $kredensial) {
    echo "http-basic untuk $host: user {$kredensial['username']}\n";
}
echo "token GitHub terdaftar untuk: " . implode(", ", array_keys($auth["github-oauth"])) . "\n";

Output yang Dihasilkan:

text
http-basic untuk git.example.com: user budi
token GitHub terdaftar untuk: github.com

Path Repositori untuk Pengembangan Lokal #

Kini kita coba yang paling menyenangkan: path repository, yang menjadikan folder lokal sebagai sumber paket. Skenarionya realistis: Anda sedang mengembangkan library budi/util-rupiah dan ingin mencobanya di aplikasi toko-app tanpa mengunggah apa pun. Siapkan dua folder bersaudara:

text
pathrepo-demo/
├── util-rupiah/    # library yang sedang dikembangkan
│   ├── composer.json
│   └── src/FormatRupiah.php
└── app/            # aplikasi konsumen
    └── composer.json

composer.json milik library tidak memuat hal baru — nama, autoload PSR-4, dan lisensi proprietary untuk menandakan kode tertutup:

json
{
    "name": "budi/util-rupiah",
    "description": "Memformat angka menjadi teks rupiah",
    "type": "library",
    "license": "proprietary",
    "autoload": {
        "psr-4": {
            "Budi\\UtilRupiah\\": "src/"
        }
    }
}

Aplikasi konsumen mendaftarkan folder tetangganya sebagai sumber, lalu memerlukannya seperti paket biasa:

json
{
    "name": "budi/toko-app",
    "repositories": [
        {
            "type": "path",
            "url": "../util-rupiah"
        }
    ],
    "require": {
        "budi/util-rupiah": "@dev"
    }
}

Kendala @dev diperlukan karena folder tanpa riwayat git tidak punya tag, sehingga versinya dibaca sebagai dev-main — versi dev yang tidak lolos ambang stabilitas default. Jalankan resolusinya:

bash
cd pathrepo-demo/app
composer update

Output yang Dihasilkan:

text
Composer could not detect the root package (budi/toko-app) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
  - Locking budi/util-rupiah (dev-main 3c54dd7)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
  - Installing budi/util-rupiah (dev-main 3c54dd7): Symlinking from ../util-rupiah
Generating autoload files
No security vulnerability advisories found.

Tiga baris pada output itu layak dibedah. Baris Locking budi/util-rupiah (dev-main 3c54dd7) menunjukkan versi dev-main beserta hash isi paket — hash milik Anda akan berbeda karena dihitung dari isi folder. Baris Symlinking from ../util-rupiah adalah kuncinya: alih-alih menyalin berkas, Composer membuat tautan simbolik di dalam vendor/ yang menunjuk ke folder asli — periksa sendiri arah tautannya:

bash
readlink vendor/budi/util-rupiah

Output yang Dihasilkan:

text
../../../util-rupiah/

Baris peringatan pertama tentang root package muncul karena folder app belum menjadi repositori git, sehingga Composer tidak bisa membaca versinya dan memakai 1.0.0 — peringatan yang hilang begitu proyek di-git init dan diberi tag. Terakhir, aplikasi tinggal memakai autoloader seperti biasa:

php
<?php
require __DIR__ . "/vendor/autoload.php";

$format = new Budi\UtilRupiah\FormatRupiah();
echo $format->format(250000), "\n";
echo $format->format(1250000), "\n";

Program di atas kami uji langsung di lingkungan kerja dan mencetak Rp250.000 lalu Rp1.250.000 — class dari folder tetangga dimuat lewat autoloader tanpa satu baris require manual. Karena vendor/ berisi symlink, setiap perubahan pada util-rupiah/src/FormatRupiah.php langsung terlihat di aplikasi tanpa perintah apa pun; composer update hanya perlu diulang bila composer.json paketnya berubah.

Satis, Packagist.com, dan Repositori Artifact #

Bayangkan dua puluh proyek, masing-masing mendaftarkan sepuluh entri vcs — daftar repository yang panjang, duplikat, dan mudah salah tulis. Solusinya adalah memusatkan daftar itu pada satu sumber bertipe composer. Dua pilihan utamanya: Satis, generator repositori statis dari Composer sendiri, dan Packagist.com — layanan komersial yang bukan bagian dari Packagist.org publik. Konfigurasi Satis yang minimal tampak seperti ini:

json
{
    "name": "budi/satis",
    "homepage": "https://packages.example.com",
    "repositories": [
        {"type": "vcs", "url": "https://github.com/budi/api-client"},
        {"type": "vcs", "url": "https://github.com/budi/util-rupiah"}
    ],
    "require-all": true
}

Satis membaca daftar repositories, memindai setiap repositori git itu, lalu menghasilkan berkas packages.json statis yang Anda unggah ke server internal. Proyek-proyek cukup mendaftarkan satu entri {"type": "composer", "url": "https://packages.example.com"} untuk mendapatkan seluruh paket internal. Packagist.com memberikan hasil akhir yang sama tanpa merawat server: tim, statistik unduhan, integrasi SSO, dan pembaruan otomatis lewat webhook, dengan biaya per pengguna. Pilihan ketiga, artifact, memperlakukan sebuah folder berisi berkas .zip sebagai sumber paket — setiap zip memuat composer.json di akarnya, dan versinya dibaca dari berkas itu; cocok untuk vendor yang mengirim paket jadi tanpa akses git.

Ringkasan #

Composer menemukan paket dari sumber mana pun yang terdaftar di kunci repositories: repositori git privat bertipe vcs, folder lokal bertipe path yang dihubungkan lewat symlink, server composer seperti Satis dan Packagist.com, hingga folder zip bertipe artifact. Kredensial hidup terpisah di auth.json atau variabel COMPOSER_AUTH, tidak pernah di composer.json yang terkomit. Setelah posisi konsumen dikuasai — publik maupun privat — chapter terakhir section ini membalik posisi kita menjadi produsen: membangun dan merilis library PHP sendiri.

Referensi #