Setiap class yang kita pakai harus terdefinisi di memori sebelum baris new dieksekusi; selama ini kita menganggap langkah tersebut gratis. Pada skrip kecil, require manual memang masih terasa wajar, tetapi pola tersebut runtuh begitu proyek menampung puluhan class dan banyak titik masuk. Autoloading adalah mekanisme PHP yang membalik logika tersebut: alih-alih kita memuat semua definisi lebih dahulu, PHP yang memanggil kode kita tepat saat sebuah class pertama kali dibutuhkan. Chapter ini membahas masalah yang diselesaikan autoloading, sejarah singkatnya, cara memakai spl_autoload_register, sampai perilaku lazy loading yang dihasilkannya.

Masalah require yang Menumpuk #

Pola klasik tanpa autoloading adalah menumpuk require_once di bagian atas setiap skrip entry point — pola yang sering dijuluki require spaghetti:

php
<?php

require_once __DIR__ . "/models/Pengguna.php";
require_once __DIR__ . "/models/Pesanan.php";
require_once __DIR__ . "/models/Produk.php";
require_once __DIR__ . "/repositories/PenggunaRepository.php";
require_once __DIR__ . "/services/AuthService.php";
require_once __DIR__ . "/controllers/HomeController.php";

Daftar semacam ini punya tiga masalah yang saling memperparah. Pertama, setiap skrip baru harus menjaga salinan daftarnya sendiri, dan satu class yang lupa didaftarkan cukup untuk menghentikan aplikasi. Kedua, seluruh class dimuat meskipun satu request hanya benar-benar memakai sebagian kecil di antaranya. Ketiga, seiring folder berpindah atau struktur proyek berubah, semua path di semua skrip harus ditelusuri satu per satu — pekerjaan yang melelahkan dan rawan salah.

Sejarah Singkat __autoload #

PHP sempat menyediakan satu magic function bernama __autoload sebagai autoloader pertama: ketika sebuah class belum terdefinisi, engine memanggil __autoload() dengan nama class tersebut sebagai argumen. Solusi itu punya batasan fatal — hanya satu function __autoload yang boleh ada per request — sehingga dua library yang sama-sama memasang autoloader saling menimpa.

Sejak PHP 7.2 mekanisme ini resmi deprecated, dan pada PHP 8 ia benar-benar dihapus. Bentuk lamanya tampak seperti ini:

php
<?php

// Gaya lama — DITOLAK oleh PHP 8, jangan ditiru
function __autoload(string $class): void
{
    $berkas = __DIR__ . "/" . str_replace("\\", "/", $class) . ".php";

    if (is_file($berkas)) {
        require $berkas;
    }
}

Mendefinisikan __autoload pada PHP 8.4 langsung dihentikan dengan pesan Fatal error: __autoload() is no longer supported, use spl_autoload_register() instead bahkan sebelum skrip berjalan. Pesan tersebut sekaligus menunjuk penggantinya, dan pengganti itulah yang kita pakai sampai sekarang.

Solusi Modern dengan spl_autoload_register #

spl_autoload_register() mendaftarkan sebuah callback sebagai autoloader. Ketika interpreter menemukan nama class yang belum terdefinisi, callback terdaftar dipanggil satu per satu; setiap callback menerima nama class sebagai string fully qualified — misalnya "App\Transportasi\Kereta" — dan tugas kita hanyalah memuat berkas yang mendefinisikan class tersebut.

Pada proyek nyata, class memang tinggal di berkasnya sendiri. Supaya contoh di sini tetap dapat dijalankan sebagai satu skrip utuh, kita menulis berkas class tersebut lebih dahulu ke sebuah folder sementara, lalu membiarkan autoloader memuatnya kembali:

php
<?php
$kode = <<<'PHP'
<?php

namespace App\Transportasi;

class Kereta
{
    public function __construct(private string $nama) {}

    public function info(): string
    {
        return "Kereta {$this->nama} siap berangkat";
    }
}
PHP;

$folder = sys_get_temp_dir() . "/latihan-autoload";
$berkasClass = $folder . "/App/Transportasi/Kereta.php";

if (!is_dir(dirname($berkasClass))) {
    mkdir(dirname($berkasClass), 0777, true);
}

file_put_contents($berkasClass, $kode);

spl_autoload_register(function (string $class): void {
    $berkas = sys_get_temp_dir() . "/latihan-autoload/"
        . str_replace("\\", "/", $class) . ".php";

    if (is_file($berkas)) {
        require $berkas;
    }
});

$kereta = new App\Transportasi\Kereta("Argo Bromo Anggrek");
echo $kereta->info(), "\n";

Output yang Dihasilkan:

text
Kereta Argo Bromo Anggrek siap berangkat

Mari kita bedah bagian-bagian pentingnya:

  1. $kode = <<<'PHP' ... PHP;: definisi class App\Transportasi\Kereta disimpan sebagai string nowdoc — pada proyek nyata, inilah isi berkas Kereta.php yang terpisah.
  2. file_put_contents($berkasClass, $kode): menulis definisi tersebut ke path yang mengikuti struktur namespace, yaitu folder App/Transportasi/ dengan nama berkas Kereta.php.
  3. spl_autoload_register(function (string $class): void { ... }): mendaftarkan closure sebagai autoloader; saat dipanggil, $class berisi nama lengkap seperti "App\Transportasi\Kereta".
  4. str_replace("\\", "/", $class): mengubah pemisah namespace menjadi pemisah direktori sehingga "App\Transportasi\Kereta" berubah menjadi path App/Transportasi/Kereta.php.
  5. is_file() lalu require: berkas baru dimuat bila benar-benar ada; setelah require, definisi class tersedia di memori dan instansiasi berlanjut normal.
  6. new App\Transportasi\Kereta(...): tidak ada satu baris require manual pun di kode utama — seluruh pemuatan terjadi lewat autoloader.

Rantai Beberapa Autoloader #

Berbeda dengan __autoload, spl_autoload_register() boleh dipanggil berkali-kali. Setiap callback masuk ke sebuah rantai dan dieksekusi sesuai urutan pendaftaran; rantai berhenti begitu salah satu callback berhasil mendefinisikan class yang dicari. Pola ini memungkinkan satu autoloader untuk class aplikasi dan autoloader lain untuk class dari package vendor, keduanya hidup berdampingan.

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

spl_autoload_register(function (string $class): void {
    echo "Autoloader #2 mencari: $class\n";
});

try {
    new App\Hilang();
} catch (Error $e) {
    echo "Galat: ", $e->getMessage(), "\n";
}

Output yang Dihasilkan:

text
Autoloader #1 mencari: App\Hilang
Autoloader #2 mencari: App\Hilang
Galat: Class "App\Hilang" not found

Pada contoh di atas, kedua autoloader hanya mencetak nama class yang mereka terima dan tidak memuat apa pun. Karena class App\Hilang tidak pernah terdefinisi, kedua callback mendapat giliran — terlihat dari dua baris pencarian yang tercetak berurutan — sebelum akhirnya PHP melempar Error yang kita tangkap dengan try/catch. Jika autoloader pertama berhasil me-render class itu, autoloader kedua tidak akan pernah terpanggil.

Autoloading sebagai Lazy Loading #

Karena definisi baru dimuat pada pemakaian pertama, autoloading sekaligus berfungsi sebagai lazy loading: class yang tidak pernah dipakai dalam sebuah request tidak pernah ikut dimuat. Perhatikan urutan kejadian pada contoh berikut.

php
<?php
$kode = <<<'PHP'
<?php

namespace Log;

class Pencatat
{
    public static function catat(string $pesan): void
    {
        echo "[LOG] $pesan\n";
    }
}
PHP;

$folder = sys_get_temp_dir() . "/latihan-lazy";
$berkasClass = $folder . "/Log/Pencatat.php";

if (!is_dir(dirname($berkasClass))) {
    mkdir(dirname($berkasClass), 0777, true);
}

file_put_contents($berkasClass, $kode);

spl_autoload_register(function (string $class): void {
    echo "(autoloader memuat $class)\n";
    $berkas = sys_get_temp_dir() . "/latihan-lazy/"
        . str_replace("\\", "/", $class) . ".php";

    if (is_file($berkas)) {
        require $berkas;
    }
});

echo "1. Sebelum class dipakai, tidak ada berkas yang dimuat\n";
Log\Pencatat::catat("Aplikasi dimulai");
echo "2. Pemakaian kedua: class sudah ada di memori\n";
Log\Pencatat::catat("Aplikasi selesai");

Output yang Dihasilkan:

text
1. Sebelum class dipakai, tidak ada berkas yang dimuat
(autoloader memuat Log\Pencatat)
[LOG] Aplikasi dimulai
2. Pemakaian kedua: class sudah ada di memori
[LOG] Aplikasi selesai

Urutan outputnya menceritakan kisah lengkapnya:

  1. Baris pertama tercetak tanpa pemuatan apa pun: autoloader sudah terdaftar, tetapi belum terpanggil karena belum ada class yang dibutuhkan.
  2. Log\Pencatat::catat(...) pertama: pemakaian pertama memicu autoloader — terlihat dari baris (autoloader memuat Log\Pencatat) — yang kemudian memuat berkas dan menjalankan method-nya.
  3. Pemakaian kedua: tidak ada baris pemuatan baru; class sudah berada di memori, dan autoloader tidak dihubungi lagi.

Pada aplikasi besar dengan ratusan class, selisih pola ini nyata terasa: request yang hanya menyentuh lima class hanya membayar biaya lima class tersebut.

Ringkasan #

  • require manual tidak skalabel: daftar per skrip, semua dimuat lebih awal, dan path rawan basi.
  • __autoload adalah autoloader generasi pertama — hanya satu per request, deprecated di 7.2, dan dihapus di PHP 8.
  • spl_autoload_register() mendaftarkan callback yang menerima nama class fully qualified dan bertugas memuat berkas definisinya.
  • Autoloader boleh berjumlah banyak, berjalan sesuai urutan pendaftaran, dan berhenti saat salah satu berhasil.
  • Autoloading sekaligus menjadi lazy loading: class tak terpakai tidak pernah dimuat.

Aturan nama class → lokasi berkas pada contoh di atas masih kita karang sendiri. Begitu proyek membesar dan dependency vendor ikut campur, konvensi buatan sendiri menjadi rapuh — dan di situlah standar PSR-4 serta autoloader Composer masuk, pembahasan yang menanti pada chapter-chapter berikutnya.

Referensi #