Composer Dan Permasalahan Autoloader Di PHP


Salah satu kecenderungan dalam OOP memecah permasalahan yang dihadapi menjadi class-class kecil yang saling berkolaborasi. Pada Java, setiap class dapat dikelompokkan ke dalam package (sehingga class boleh memiliki nama yang sama asalkan berada di package yang berbeda). Untuk memakai class dalam package yang berbeda, programmer Java akan menggunakan import untuk menentukan lokasi class yang akan dipakai. Kumpulan class yang saling berkaitan (misalnya sebuah komponen) didistribusikan dalam bentuk file JAR. Programmer dapat me-load class secara dinamis dengan menggunakan ClassLoader, tapi biasanya dependensi dideklarasikan secara statis dengan memberitahu lokasi file JAR yang dibutuhkan melalui classpath.

PHP memiliki penanganan package dan class yang tidak sesederhana di Java. Hal ini karena bahasa tersebut awalnya adalah bahasa scripting dengan asumsi bahwa satu halaman PHP adalah sebuah halaman web. Seiring waktu, PHP menjadi semakin kompleks dan banyak library OOP pun bermunculan. Untuk memakai class dalam library eksternal, programmer perlu memberikan perintah include atau require agar file yang berisi deklarasi class dibaca. Hal ini karena biasanya programmer membuat sebuah class dalam sebuah file PHP tunggal. Selain itu, class umumnya dideklarasikan dalam namespace berbeda sehingga programmer perlu memberikan perintah use untuk memakai namespace yang bersangkutan. Jadi ribet, bukan?

Untuk mengatasi permasalahan ini, PHP 5 memperkenalkan fungsi spl_autoload_register() yang akan mengerjakan sebuah function bila sebuah class tidak dikenal dipanggil. Tapi kembali muncul sebuah masalah baru: spl_autoload_register() hanya menyediakan cara untuk mencari class, tapi tidak ada standar yang pasti mengenai lokasi file PHP yang berisi class tersebut. Hal ini berbeda dengan Java dimana setiap package diwakili oleh sebuah folder/direktori sehingga lokasi file class untuk sebuah package dapat ditelusuri secara mudah. PHP tidak membatasi sebuah namespace harus memiliki struktur folder serupa. Programmer bisa bebas meletakkan file PHP berisi class di lokasi mana saja. Lalu, bagaimana cara universal untuk mencari lokasi file PHP berdasarkan informasi namespace?

Untuk itu, PHP-FIG (PHP Framework Interop Group), http://www.php-fig.org/faq/, menciptakan standar seperti PSR-0 (Autoloading Standar) dan PSR-4 (Improved Autoloading). Pada standar PSR-0, namespace seperti \Doctrine\Common\IsolatedClassLoader akan dipetakan pada folder /path/to/project/lib/vendor/Doctrine/Common/IsolatedClassLoader.php. Hal ini mirip seperti pada pemetaan folder untuk package di Java. Standar PSR-4 memperkenalkan sesuatu yang berbeda dimana namespace terdiri atas namespace prefix yang dapat dipetakan ke lokasi apa saja dan sisanya merupakan subfolder dari prefix tersebut. Walaupun lebih fleksibel, PSR-4 membuat lebih sulit menebak lokasi file PHP. Sebagai contoh, anggap saja terdapat class: Foo\Bar\Bat\Baz. Si pembuat class kemudian memilih Foo\Bar sebagai namespace prefix. Namespace prefix Foo\Bar dapat dipetakan pada satu atau lebih folder (misalnya /path/to/packages/foo-bar/src dan /path/to/packages/foo-bar/tests). Jadi, untuk mencari file PHP yang mengandung Foo\Bar\Bat\Baz, autoloader harus memeriksa apakah ada file /path/to/packages/foo-bar/src/Bat/Baz.php atau /path/to/packages/foo-bar/tests/Bat/Baz.php.

Btw, PHP-FIG dan standar PSR (PHP Standards Recommendation) dari mereka bukanlah bagian resmi dari PHP. Sebagai contoh, RFC untuk memasukkan PSR-0 menjadi bagian dari PHP telah ditolak seperti yang dapat dilihat di https://wiki.php.net/rfc/splclassloader.

Permasalahan berikutnya adalah siapa yang harus membuat isi function yang didaftarkan dengan memanggil spl_autoload_register()? Function ini harus mencari lokasi file PHP yang berisi deklarasi class yang berada dalam namespace berbeda. Mendaftarkan setiap lokasi folder untuk namespace prefix di PSR-4 bisa jadi sesuatu yang membosankan. Beruntungnya, Composer memiliki fasilitas untuk membuat implementasi function yang didaftarkan melalui spl_autoload_register() secara otomatis. Yang perlu saya lakukan tinggal memanggil (dengan keyword include atau require) file vendor/autoload.php.

Untuk melakukan konfigurasi autoloader di Composer, saya dapat mengubah bagian dari composer.json yang terlihat seperti berikut ini:

"autoload": {
    "classmap": [
        "app/commands",
        "app/controllers",
        "app/models",
        "app/database/migrations",
        "app/database/seeds",
        "app/tests/TestCase.php"
    ]
},

Composer mendukung PSR-0 autoloading, PSR-4 autoloading, classmap generation dan files includes.

Pada contoh di atas, yang merupakan bawaan dari proyek Laravel, konfigurasi autoloader dilakukan dengan menggunakan classmap. Pada konfigurasi ini, Composer akan memeriksa isi direktori yang diberikan dan membuat sebuah pemetaan dari nama class ke nama file PHP yang dapat dijumpai di vendor/composer/autoload_classmap.php. Karena sudah pemetaan yang tinggal dicari, metode ini merupakan metode yang paling cepat dan tidak membutuhkan algoritma tambahan seperti pada PSR-0/4. Kelemahannya adalah setiap kali ada file baru yang dibuat pada direktori, saya harus memberikan perintah untuk memperbaharui isi vendor/composer/autoload_classmap.php seperti berikut ini:

composer dump-autoload

Tunggu dulu, pengguna tidak harus mengerjakan perintah di atas bila menambah class tertentu di proyek Laravel mereka! Mengapa demikian? Hal ini karena selain memakai autoloader dari Composer, Laravel juga mendaftarkan autoloader miliknya sendiri di Illuminate\Support\ClassLoader. Autoloader tersebut akan mencari file PHP di lokasi folder yang terdaftar di app/start/global.php seperti pada cuplikan berikut ini:

/*
|--------------------------------------------------------------------------
| Register The Laravel Class Loader
|--------------------------------------------------------------------------
|
| In addition to using Composer, you may use the Laravel class loader to
| load your controllers and models. This is useful for keeping all of
| your classes in the "global" namespace without Composer updating.
|
*/

ClassLoader::addDirectories(array(

    app_path().'/commands',
    app_path().'/controllers',
    app_path().'/models',
    app_path().'/database/seeds',

));

Walaupun framework seperti Symfony dan Laravel memakai nama ClassLoader untuk autoloader mereka, perlu diingat bahwa autoloader PHP berbeda dengan ClassLoader di Java. Pada Java, beberapa ClassLoader berbeda dapat membaca class yang sama tapi dengan versi berbeda tanpa mengalami bentrokan asalkan mereka dipakai pada context ClassLoader masing-masing. Pada PHP, autoloader biasanya hanya mencari dan men-include file PHP berdasarkan urutan registrasinya. Sebagai contoh, saya memakai PHPUnit bawaan Zend Studio untuk menguji sebuah proyek Laravel. PHPUnit ini memakai Composer versi lama yang belum mendukung PSR-4. Sementara itu, proyek Laravel memakai Composer versi baru yang sudah mendukung PSR-4. Pada saat saya menjalankan pengujian melalui PHPUnit tersebut, saya akan memperoleh kesalahan seperti:

Debug Error: /proyek/vendor/composer/autoload_real.php line 40 - Call to undefined method Composer\Autoload\ClassLoader::setPsr4()

Mengapa demikian? Karena yang ditemukan terlebih dahulu oleh autoloader adalah Composer\Autoload\ClassLoader versi lama milik PHPUnit bawaan Zend Studio, sementara itu Composer\Autoload\ClassLoader milik Composer yang ter-install diproyek saat ini akan diabaikan (padahal ini yang dibutuhkan!). Padahal, permasalahan seperti ini adalah salah satu alasan memakai custom ClassLoader di Java.

Tentang Solid Snake
I'm nothing...

Apa komentar Anda?