Bayangkan proyek dengan 300 class dan sebuah berkas yang memuat daftar 300 baris require. Setiap class baru harus didaftarkan, setiap pemindahan berkas harus diperbarui, dan satu baris yang tertinggal menjadi fatal error saat runtime. PSR-4 adalah standar yang menghapus daftar tersebut: sebuah kesepakatan tentang bagaimana namespace dipetakan ke lokasi berkas, sehingga lokasi class dapat dihitung, bukan didaftarkan. Pada chapter ini kita fokus pada sisi standarnya — aturan pemetaan, struktur prefix, dan sensitivitas kapital — sementara mekanisme autoloading (spl_autoload_register() dan implementasi Composer) sudah kita bahas pada chapter PSR-4 Autoloading di Section Namespaces dan Autoloading
.
Aturan Dasar PSR-4 #
Inti PSR-4 adalah algoritma empat langkah yang mengubah nama class lengkap menjadi path berkas:
- Sebuah nama class lengkap (FQCN) berbentuk
\<NamespacePrefix>(\<SubNamespace>)*<NamaClass>— misalnyaApp\League\Clashterdiri dari prefixApp, sub-namespaceLeague, dan nama classClash. - Namespace prefix dipetakan ke sebuah direktori dasar (base directory) yang ditentukan proyek.
- Setiap sub-namespace setelah prefix dikonversi menjadi subdirektori, mengikuti urutannya.
- Terminating class name — segmen paling akhir — menjadi nama berkas dengan akhiran
.php, dan keduanya harus sama persesuai.
Pemetaan itu membuat tabel berikut dapat dibaca dua arah: dari nama class Anda tahu path berkas, dan dari path berkas Anda tahu nama class:
| Nama class lengkap | Prefix | Direktori dasar | Lokasi berkas |
|---|---|---|---|
App\League\Clash | App\ | src/ | src/League/Clash.php |
App\Controller\HomeController | App\ | src/ | src/Controller/HomeController.php |
App\League\Clash | App\League\ | src/ | src/Clash.php |
App\Tests\Unit\InvoiceTest | App\Tests\ | tests/ | tests/Unit/InvoiceTest.php |
Baris ketiga memperlihatkan sifat penting PSR-4: prefix yang dipetakan boleh lebih panjang dari satu segmen. Ketika prefix-nya App\League\, sub-namespace League tidak lagi menjadi direktori karena bagian itu sudah “dikonsumsi” prefix — sisanya hanya Clash menjadi berkas.
Menghitung Path secara Manual #
Cara terbaik memahami algoritma ini adalah menjalankannya sendiri. Fungsi kecil berikut menerima prefix, direktori dasar, dan nama class, lalu mengembalikan path yang dicari autoloader:
<?php
function psr4KePath(string $prefix, string $direktoriDasar, string $namaKelas): string
{
$bagianRelatif = substr($namaKelas, strlen($prefix));
return $direktoriDasar . str_replace('\\', '/', $bagianRelatif) . '.php';
}
echo psr4KePath('App\\', 'src/', 'App\\League\\Clash') . "\n";
echo psr4KePath('App\\', 'src/', 'App\\Controller\\HomeController') . "\n";
echo psr4KePath('App\\League\\', 'src/', 'App\\League\\Clash') . "\n";Output yang Dihasilkan:
src/League/Clash.php
src/Controller/HomeController.php
src/Clash.phpMari kita bedah fungsinya:
- Baris 3:
substr($namaKelas, strlen($prefix))membuang prefix dari nama class dan menyisakan bagian relatif — untukApp\League\Clashdengan prefixApp\, sisanya\League\Clash. - Baris 5:
str_replace('\\', '/', ...)mengubah pemisah namespace menjadi pemisah direktori, lalu direktori dasar dan akhiran.phpdirangkai menjadi path lengkap. - Baris 7-9: tiga pemanggilan menghasilkan tiga path berbeda — dua pemetaan prefix pendek dan satu pemetaan prefix dalam, persis seperti tabel sebelumnya.
Perhatikan bahwa dalam string PHP satu huruf, backslash ditulis ganda ('App\\') karena backslash adalah karakter escape — \\ merepresentasikan satu backslash sungguhan.
Autoloader di Balik Layar #
Siapa yang menjalankan algoritma tersebut saat program berjalan? Jawabannya autoloader: sebuah callback yang didaftarkan lewat spl_autoload_register() dan dipanggil PHP setiap kali class yang belum dikenal dirujuk. Demo berikut mendaftarkan autoloader sederhana yang hanya menampilkan path yang dicarinya:
<?php
spl_autoload_register(function (string $namaKelas): void {
$prefix = 'App\\';
$direktoriDasar = 'src/';
if (strncmp($namaKelas, $prefix, strlen($prefix)) !== 0) {
return;
}
$bagianRelatif = substr($namaKelas, strlen($prefix));
$berkas = $direktoriDasar . str_replace('\\', '/', $bagianRelatif) . '.php';
echo "Autoloader mencari berkas: {$berkas}\n";
});
var_dump(class_exists('App\\League\\Clash'));Output yang Dihasilkan:
Autoloader mencari berkas: src/League/Clash.php
bool(false)Mari kita telusuri jalannya:
- Baris 3:
spl_autoload_register()mendaftarkan closure sebagai autoloader; PHP menyimpannya untuk dipanggil saat class hilang. - Baris 7-9:
strncmp()membandingkan awalan nama class dengan prefix — jika tidak cocok, autoloader mundur diam-diam karena class itu bukan tanggung jawabnya. - Baris 16:
class_exists('App\League\Clash')memicu autoloader; autoloader mencetak path tujuannya namun tidak memuat berkas apa pun karena berkasnya memang tidak ada, sehinggaclass_exists()mengembalikanbool(false).
Autoloader di proyek nyata tentu tidak berhenti pada echo — autoloader sungguhan menyertakan berkasnya. Anda juga hampir tidak pernah menulisnya manual karena Composer yang membangkitkannya dari deklarasi autoload di composer.json:
{
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"App\\Tests\\": "tests/"
}
}
}Bagian autoload memetakan kode produksi: class App\League\Clash dicari di src/League/Clash.php. Bagian autoload-dev memetakan class khusus pengembangan — biasanya test — sehingga App\Tests\Unit\InvoiceTest dicari di tests/Unit/InvoiceTest.php dan tidak ikut terbawa ke produksi. Setiap kali pemetaan berubah, jalankan composer dump-autoload untuk membangkitkan ulang autoloader-nya.
Variasi Struktur Prefix #
PSR-4 membebaskan proyek memilih prefix sedalam apa yang dipetakan. Bentuk paling umum adalah prefix pendek di level src/ — seluruh class hidup di bawah satu pohon direktori dan path selalu menjadi cermin namespace. Bentuk kedua adalah prefix yang lebih dalam untuk sub-tree tertentu, seperti memetakan App\League\ langsung ke src/ sehingga App\League\Clash cukup berada di src/Clash.php.
Ketika beberapa prefix hidup berdampingan, prefix terpanjang yang cocok dengan nama class yang dipakai. Strategi ini berguna untuk paket yang menyatukan beberapa komponen, tetapi berhati-hatilah: prefix dalam membuat path tidak lagi membayangkan namespace penuh, dan pembaca baru bisa salah menebak lokasi berkas. Jika tidak ada kebutuhan khusus, mulailah dengan satu prefix pendek — struktur paling membosankan sering kali adalah struktur paling mudah dipelihara.
Sensitivitas Kapital #
Aturan “nama berkas harus sama persis dengan nama class” berlaku hingga ke kapitalisasi. Clash.php dan clash.php adalah dua berkas berbeda menurut PSR-4, meskipun sistem operasi Anda berpendapat lain.
Riwayat Singkat PSR-0 #
PSR-4 punya pendahulu: PSR-0, standar autoloading tahun 2010 yang kini berstatus deprecated. PSR-0 menuntut struktur direktori yang lebih kaku — setiap underscore dalam nama class diterjemahkan menjadi pemisah direktori, dan namespace paling depan wajib menjadi direktori nyata di root proyek:
App_League_Clash → ./App/League/Clash.phpAturan underscore ini mewarisi gaya penamaan era Zend Framework lama, tetapi menjadi beban saat komunitas beralih ke namespace modern. PSR-4 (diterima 2013) menghapus aturan tersebut dan memperbolehkan pemetaan prefix ke direktori mana pun, termasuk src/. Proyek baru sebaiknya tidak lagi memakai PSR-0; Anda cukup mengenalnya karena masih menjumpainya di kode warisan.
Ringkasan #
PSR-4 menghubungkan namespace dengan struktur berkas melalui algoritma sederhana: prefix dipetakan ke direktori dasar, sub-namespace menjadi subdirektori, dan nama class menjadi nama berkas .php — semuanya sensitif terhadap kapitalisasi. Composer menjalankan pemetaan ini lewat autoload dan autoload-dev, menggantikan daftar require manual dengan satu perintah composer dump-autoload. Standar ini menjawab “di mana berkas berada”; pertanyaan “bagaimana simbol-simbol itu dinamai” kita jawab pada chapter Naming Conventions berikutnya.