Sepanjang section ini posisi kita adalah konsumen: membaca composer.json orang lain, memasang paketnya, mengaudit celahnya. Posisi itu kini dibalik. Setiap developer PHP pada akhirnya menulis kode yang layak dipakai ulang — formatter internal, wrapper API, utility string — dan menjadikannya library berarti kode itu mendapatkan versi sendiri, changelog sendiri, dan siklus hidup yang tidak terikat pada satu aplikasi. Pada chapter penutup section ini kita membangun library kecil bernama budi/slug-generator sampai tuntas: merancang API-nya, menyiapkan composer.json dengan autoload PSR-4, menguji autoloader-nya dengan Composer sungguhan, memberi nomor versi lewat git tag, lalu memahami jalur publikasinya ke Packagist.
Library Bukan Aplikasi Kecil #
Library dan aplikasi dibedakan oleh satu hal: siapa pemanggilnya. Aplikasi dipanggil pengguna akhir melalui browser atau CLI, sedangkan library dipanggil kode developer lain lewat autoloader. Konsekuensinya menjalar ke mana-mana:
| Aspek | Aplikasi | Library |
|---|---|---|
| Titik masuk | front controller seperti index.php | tidak ada; dimuat lewat vendor/autoload.php |
| Konsumen | pengguna akhir | developer lain lewat composer require |
| Dependensi | dipasang agar aplikasi berjalan | seminimal mungkin agar tidak membebani pengguna |
| Konfigurasi | milik aplikasi sendiri | disuntikkan pengguna lewat constructor atau parameter |
Baris terakhir tabel itu paling sering dilanggar pemula: library yang membaca konfigurasi dari berkas tetunya sendiri, membuka koneksi database sendiri, atau bahkan membaca $_POST sendiri. Library yang baik murni menerima masukan lewat parameter dan constructor, lalu mengembalikan hasil — perilaku yang juga membuat library mudah diuji. Aturan praktisnya: library tidak boleh tahu di aplikasi macam apa ia hidup.
Merancang API yang Kecil #
Library kita mengubah judul berbahasa bebas menjadi slug URL yang ramah pencarian: huruf kecil, tanpa spasi, hanya alfanumerik dan pemisah. Seluruh isi librarynya satu class:
<?php
declare(strict_types=1);
namespace Budi\SlugGenerator;
final class SlugGenerator
{
public function __construct(
private readonly string $separator = '-',
private readonly int $panjangMaks = 80,
) {
}
public function generate(string $text): string
{
$slug = mb_strtolower(trim($text));
$slug = preg_replace('/[^a-z0-9]+/u', $this->separator, $slug);
$slug = trim($slug, $this->separator);
if ($slug === '') {
throw new \InvalidArgumentException('Teks tidak boleh kosong');
}
if (mb_strlen($slug) > $this->panjangMaks) {
$slug = rtrim(mb_substr($slug, 0, $this->panjangMaks), $this->separator);
}
return $slug;
}
}Mari kita bedah keputusan-keputusan desainnya:
namespace Budi\SlugGenerator— identitas paket; nanti dipetakan ke foldersrc/lewat aturan PSR-4 dicomposer.json.- Constructor dengan dua nilai bawaan — pemisah dan panjang maksimum bisa diganti lewat named arguments tanpa membuat class menjadi rumit;
finalmencegah pewarisan yang tidak kita rancang. mb_strtolower()danmb_strlen()— versi multibyte dari fungsi string, karena judul bisa berisi karakter di luar ASCII;preg_replace()dengan modifierumemastikan regex bekerja pada UTF-8.throw new \InvalidArgumentException— masukan yang tidak bisa dijadikan slug ditolak eksplisit alih-alih mengembalikan string kosong yang menyesatkan pemanggil.
Pemakaiannya kemudian semurni mungkin — buat object, panggil satu method:
<?php
require __DIR__ . "/src/SlugGenerator.php";
use Budi\SlugGenerator\SlugGenerator;
$generator = new SlugGenerator();
echo $generator->generate("Belajar PHP Dasar"), "\n";
echo $generator->generate("Kopi Gayo @ Rp75.000!"), "\n";
$pendek = new SlugGenerator(panjangMaks: 18);
echo $pendek->generate("Membangun Aplikasi Web Modern dengan PHP"), "\n";
$underscore = new SlugGenerator("_");
echo $underscore->generate("Belajar PHP Dasar"), "\n";
try {
$pendek->generate(" ");
} catch (InvalidArgumentException $e) {
echo "Ditolak: ", $e->getMessage(), "\n";
}Output yang Dihasilkan:
belajar-php-dasar
kopi-gayo-rp75-000
membangun-aplikasi
belajar_php_dasar
Ditolak: Teks tidak boleh kosongKarakter @, tanda baca, dan spasi berubah menjadi satu pemisah; opsi panjangMaks: 18 memotong slug panjang tanpa meninggalkan pemisah menggantung; constructor pertama dipakai sebagai posisi $separator sehingga new SlugGenerator("_") menghasilkan slug bergaya underscore; dan teks kosong ditolak dengan exception. Berkas contoh.php pada bundle chapter merangkum seluruh contoh ini dalam satu program yang bisa dijalankan langsung tanpa Composer.
composer.json Sebuah Library #
Sekarang kita beri library itu identitas paket. composer.json milik library memuat empat hal yang saling terkait:
{
"name": "budi/slug-generator",
"description": "Mengubah judul menjadi slug URL yang ramah pencarian",
"type": "library",
"license": "MIT",
"require": {
"php": "^8.1"
},
"autoload": {
"psr-4": {
"Budi\\SlugGenerator\\": "src/"
}
}
}Nama paket terdiri atas vendor dan produk, keduanya huruf kecil dan dipisah garis miring — budi adalah vendor Anda, dan di dunia nyata sebaiknya nama unik seperti akun GitHub Anda agar tidak bertabrakan di Packagist. Bagian "require" sengaja hanya memuat "php": "^8.1": batas bawah versi PHP yang benar-benar dibutuhkan fiturnya (property promotion dan readonly dari 8.1), tanpa satu pun dependensi lain — library utilitas semakin ramping semakin menarik dipasang. Kendala versi seperti ^8.1 sudah kita bedah tuntas di chapter Version Constraints
.
Kunci "autoload" adalah jantungnya: aturan PSR-4 menyatakan namespace Budi\SlugGenerator\ dipetakan ke folder src/, sehingga class Budi\SlugGenerator\SlugGenerator dicari di src/SlugGenerator.php. Mekanisme lengkap pemetaan ini — termasuk mengapa backslash ditulis ganda di JSON — pernah kita praktikkan di chapter Autoloading dengan Composer
. Nilai "type": "library" memberi tahu installer bahwa paket ini adalah pustaka, bukan proyek yang bisa dijalankan; "license": "MIT" menyatakan izin pakai bagi pengguna — dua nilai yang wajib jujur bila suatu hari paket dipublikasikan.
Menguji Autoloader #
Struktur akhir library kita di /tmp/lib-demo/slug-generator:
slug-generator/
├── composer.json
├── demo.php
└── src/
└── SlugGenerator.phpJalankan Composer pertama kali di folder tersebut:
cd slug-generator
composer installOutput yang Dihasilkan:
Composer could not detect the root package (budi/slug-generator) version, defaulting to '1.0.0'. See https://getcomposer.org/root-version
No composer.lock file present. Updating dependencies to latest instead of installing from lock file. See https://getcomposer.org/install for more information.
Loading composer repositories with package information
Updating dependencies
Nothing to modify in lock file
Writing lock file
Installing dependencies from lock file (including require-dev)
Nothing to install, update or remove
Generating autoload filesTiga kejadian pada output itu sebenarnya materi chapter-chapter sebelumnya yang kini berbalik arah. Peringatan root package muncul karena folder belum menjadi repositori git, sehingga versi paket tidak terbaca. Ketiadaan composer.lock membuat composer install beralih menjadi pembaruan — perilaku yang kita bahas di chapter composer.lock
. Dan karena satu-satunya dependensi adalah PHP itu sendiri, tidak ada paket yang diunduh; yang penting justru baris terakhir: Generating autoload files, yang menghasilkan folder vendor/ berisi autoloader dari aturan PSR-4 kita. Bukti akhirnya adalah program pemakai yang tidak lagi memuat berkas class secara manual:
<?php
require __DIR__ . "/vendor/autoload.php";
use Budi\SlugGenerator\SlugGenerator;
$generator = new SlugGenerator();
echo $generator->generate("Halo Composer Library"), "\n";Ketika dijalankan dengan php demo.php di mesin uji kami, program di atas mencetak halo-composer-library — namespace lengkap terpetaikan otomatis ke src/SlugGenerator.php tanpa satu baris require untuk class-nya. Autoloader yang dihasilkan Composer sudah menghitung lokasi setiap class dari aturan PSR-4; menambah class baru ke folder src/ langsung tersedia tanpa konfigurasi tambahan.
Penomoran Versi dengan Git Tag #
Peringatan root package di atas menunjuk pada satu pekerjaan yang belum: memberi library itu identitas versi. Sumbernya adalah tag git — mekanisme yang sama yang dipakai Composer saat membaca paket vcs pada chapter sebelumnya. Alurnya: inisialisasi repositori, komit isi library, lalu beri tag pada titik yang layak disebut rilis pertama:
git init -q
git add composer.json src/
git commit -m "feat: slug generator pertama"
git tag v1.0.0Tag v1.0.0 menandai rilis stabil pertama. Nantinya, setelah Anda menambahkan fitur baru yang kompatibel-mundur — misalnya opsi pemisah kustom — komit perubahan itu lalu naikkan tag minor:
git add -A
git commit -m "feat: opsi pemisah kustom"
git tag v1.1.0Yang dibaca Composer hanyalah daftar tag-nya:
git tag -lOutput yang Dihasilkan:
v1.0.0
v1.1.0Dengan dua tag itu, library Anda kini menawarkan versi 1.0.0 dan 1.1.0 kepada konsumen — persis seperti paket-paket yang selama ini Anda pasang. Disiplin penomorannya mengikuti semantik yang sama: v1.0.0 untuk rilis pertama yang stabil, v1.1.0 untuk penambahan fitur yang kompatibel-mundur, dan v2.0.0 ketika ada perubahan yang merusak panggilan lama. Sekali tag ter-publish, isinya tidak boleh diubah — perbaikan apa pun bergerak lewat tag baru, karena konsumen berhak atas janji bahwa 1.0.0 mereka selalu berisi kode yang sama.
Publikasi ke Packagist #
Untuk library terbuka, titik akhirnya adalah Packagist. Alurnya ringkas: dorong repositori ke GitHub atau GitLab, buat akun di packagist.org, kirim URL repositori melalui halaman Submit, lalu pasang webhook yang diberikan Packagist ke repositori. Sejak webhook aktif, setiap git push yang membawa tag baru otomatis memperbarui daftar versi — konsumen tinggal menjalankan composer update untuk mendapatkannya. Nama vendor di composer.json sebaiknya dicocokkan dengan nama akun Anda agar klaim kepemilikan paket jelas.
Proyek Terpandu Library #
Chapter ini membangun library dalam bentuk paling ringkas agar setiap keputusan terlihat. Proyek terpandu Membangun Library PHP Reusable mengambil jalur penuh: merancang struktur paket, namespace, dan public API; menulis test suite PHPUnit; menjalankan static analysis; hingga build dan publishing. Menyelesaikan proyek itu setelah chapter ini adalah latihan terbaik sebelum Anda merilis library pertama milik Anda sendiri.
Ringkasan #
Library adalah paket tanpa titik masuk yang hidup dari kontrak bersih: API kecil yang menerima masukan lewat parameter, composer.json dengan nama vendor, lisensi jujur, dan autoload PSR-4 ke folder src/. Autoloader diuji dengan composer install, versi diberikan lewat git tag yang mengikuti semantik, dan publikasi terbuka berakhir di Packagist sementara kode internal berjalan lewat repositories privat. Dengan ini kompetensi Composer Anda menutup lingkaran penuh dari konsumen sampai produsen — dan Section berikutnya beralih ke sisi lain aplikasi: menerima data dari pengguna melalui form HTML.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
contoh.php
Unduh berkas<?php
declare(strict_types=1);
require __DIR__ . '/src/SlugGenerator.php';
use Budi\SlugGenerator\SlugGenerator;
$generator = new SlugGenerator();
echo $generator->generate('Belajar PHP Dasar'), "\n";
echo $generator->generate('Kopi Gayo @ Rp75.000!'), "\n";
echo $generator->generate(' -- Rasa Nusantara -- '), "\n";
$pendek = new SlugGenerator(panjangMaks: 18);
echo $pendek->generate('Membangun Aplikasi Web Modern dengan PHP'), "\n";
$underscore = new SlugGenerator('_');
echo $underscore->generate('Belajar PHP Dasar'), "\n";
try {
$pendek->generate(' ');
} catch (InvalidArgumentException $e) {
echo 'Ditolak: ', $e->getMessage(), "\n";
}
src/SlugGenerator.php
Unduh berkas<?php
declare(strict_types=1);
namespace Budi\SlugGenerator;
final class SlugGenerator
{
public function __construct(
private readonly string $separator = '-',
private readonly int $panjangMaks = 80,
) {
}
public function generate(string $text): string
{
$slug = mb_strtolower(trim($text));
$slug = preg_replace('/[^a-z0-9]+/u', $this->separator, $slug);
$slug = trim($slug, $this->separator);
if ($slug === '') {
throw new \InvalidArgumentException('Teks tidak boleh kosong');
}
if (mb_strlen($slug) > $this->panjangMaks) {
$slug = rtrim(mb_substr($slug, 0, $this->panjangMaks), $this->separator);
}
return $slug;
}
}